ref: 7eeca4c25afb01df3f134237014f9583975fc608
parent: c2fc0147298b582aeb55eaed59f9ac0c4c2860cf
parent: cb96e45d2b5094c69a4cb0567ca2391d79533d00
author: penny64 <penny64@noreply.codeberg.org>
date: Wed Nov 5 21:29:20 EST 2025
Merge pull request 'Create a CMD processing system and port every command to it' (#14) from cmd_structure into main Reviewed-on: https://codeberg.org/penny64/hellclient/pulls/14
--- a/commands.go
+++ b/commands.go
@@ -1,12 +1,20 @@
package main
import (
+ "fmt"
+ "runtime/debug"
"strings"
+ "time"
+ "context"
+ "strconv"
+
+ "github.com/k3a/html2text"
+ mastodon "codeberg.org/penny64/hellclient-go-mastodon"
)
var commands = []string{"examine", "reply", "like", "thread", "open", "prev", "download", "dm", "rt", "hrt", "parent", "children", "rm", "mark", "unmark", "account", "import", "pause", "resume", "url", "fpost", "ufpost", "edit", "notice", "stats", "next", "view", "bookmarks", "follow", "unfollow", "likes", "help", "reload", "attach", "detach", "pinned", "cat", "play", "translate", "read", "version", "local", "public", "block", "unblock", "unlike", "home", "page", "profile"}-func processInput(input string) (command string, arguments string, found bool) {+func processInput(input string, commands []string) (command string, arguments string, found bool) { if input == "" {command = ""
@@ -89,4 +97,1179 @@
}
}
return
+}
+
+type cmdflag uint8
+
+const (
+ free cmdflag = 1 << iota
+ status
+ account
+ argument
+ // Resolve @user@domain stringfs for us
+ acc_resolve
+ // Load user's own most recent post if they have one
+ want_recent
+ // Set up a templater for us
+ templater
+ // Load url from url index
+ load_url
+)
+
+type cmdloader struct {+ hc *Hellclient
+ lastindex string
+ lastaccount *mastodon.Account
+ commands []string
+ cmdmap map[string]cmder
+}
+
+func (loader *cmdloader) init(cmds []cmder) {+ loader.cmdmap = make(map[string]cmder)
+ for i, _ := range cmds {+ name := cmds[i].name()
+ loader.commands = append(loader.commands, name)
+ loader.cmdmap[name] = cmds[i]
+ }
+}
+
+// Cases
+// Return loaded account if tag was found or was empty but loaded
+// Return loaded account if index was empty
+// Load lastaccount if it exists or try to look one up, return account or nil
+func (data *cmddata) lookupAccount(loader *cmdloader) *mastodon.Account {+
+ if data.found_index {+ return data.account
+ }
+ if data.index == "" && data.account != nil {+ return data.account
+ }
+
+ account := loader.lastaccount
+ if account == nil {+ account = loader.hc.resolveAccount(data.index)
+ if account == nil {+ return nil
+ }
+ }
+
+ loader.lastaccount = account
+ loader.lastindex = ""
+ return account
+}
+
+func (loader *cmdloader) run(line string) (string, error) {+ data, err := loader.processLine(line)
+ if err != nil {+ return "", nil
+ }
+ hc := loader.hc
+ if !data.cmd_found {+ return "", fmt.Errorf("Command %s not found.\n", data.command)+ }
+ flags := loader.cmdmap[data.command].flags()
+
+ // Command needs us to resolve @user@domain targets as indexes
+ if flags&acc_resolve != 0 {+ data.account = data.lookupAccount(loader)
+ if data.status != nil && data.status.ID != data.account.ID {+ data.status = nil
+ }
+ }
+
+ // Command wants us to load the user's most recently made status
+ if flags&want_recent != 0 && !data.found_index {+ if hc.recentpost != nil {+ data.status = hc.recentpost
+ data.account = &hc.recentpost.Account
+ }
+ }
+
+ // Command needs us to set up a post templater
+ if flags&templater != 0 && data.status != nil {+ formatter := &StatusFormatter{prefs: hc.preferences, status: data.status, postContext: hc.ctxref, localindex: data.index}+ templater := newStatusTemplateRenderer(formatter)
+ data.templater = templater
+ }
+
+ // Command needs targeturl set
+ if flags&load_url != 0 && data.status != nil {+ urlindex, err := strconv.Atoi(data.content)
+ if err != nil {+ urlindex = 1
+ }
+ if urlindex <= len(hc.homeref.urlmap[data.index]) {+ data.targeturl = hc.homeref.urlmap[data.index][urlindex-1]
+ }
+ }
+
+ err = data.checkReqs(loader.cmdmap[data.command].flags())
+ if err != nil {+ return "", err
+ }
+ return loader.cmdmap[data.command].result(data), nil
+}
+
+func (loader *cmdloader) processLine(line string) (*cmddata, error) {+ command, arguments, found := processInput(line, loader.commands)
+ index, content, _ := strings.Cut(arguments, " ")
+ // "." refers to most recently acted on status
+ // but if we have an account set, we don't want to set an index!
+ var dot_index bool
+ if index == "." {+ dot_index = true
+ }
+ if index == "." && loader.lastaccount == nil {+ index = loader.lastindex
+ }
+
+ postItem, postOK := loader.hc.homeMap[index]
+ foundindex := false
+ //If there's no index selected load the last post we operated on
+ if postOK {+ foundindex = true
+ loader.lastindex = index
+ loader.lastaccount = nil
+ } else {+ postItem, postOK = loader.hc.homeMap[loader.lastindex]
+ }
+ var reblogger *mastodon.Status
+ //Okay now see if the post we end up with is a reblog
+ if postOK {+ if postItem.Reblog != nil {+ reblogger = postItem
+ postItem = postItem.Reblog
+ }
+ }
+
+ cmdctx := &cmddata{+ command: command,
+ content: content,
+ raw_argument: arguments,
+ dot_index: dot_index,
+ found_index: foundindex,
+ index: index,
+ cmd_found: found,
+ }
+ if loader.lastaccount != nil {+ cmdctx.account = loader.lastaccount
+ }
+ if postOK {+ cmdctx.status = postItem
+ cmdctx.account = &postItem.Account
+ cmdctx.reblogger = reblogger
+
+ }
+
+ return cmdctx, nil
+}
+
+type cmddata struct {+ status *mastodon.Status
+ account *mastodon.Account
+ reblogger *mastodon.Status
+ raw_argument string
+ command string
+ cmd_found bool
+ content string
+ found_index bool
+ dot_index bool
+ index string
+ targeturl string
+ templater *templateRenderer
+}
+
+
+type cmder interface {+ name() string
+ flags() cmdflag
+ result(data *cmddata) string
+}
+
+
+// return an error message if cmddata matches cmders flags
+func (cmd *cmddata) checkReqs(flags cmdflag) (err error) {+ if flags&free != 0 {+ return nil
+ }
+ if (flags&status != 0) && (flags&account != 0) {+ if cmd.status == nil && cmd.account == nil {+ return fmt.Errorf("%s requires a status or an account\n", cmd.command)+ }
+ } else {+ if (flags&status != 0) && (cmd.status == nil) {+ return fmt.Errorf("%s requires a status\n", cmd.command)+ }
+ if (flags&account != 0) && (cmd.account == nil) {+ return fmt.Errorf("%s requires an account\n", cmd.command)+ }
+ }
+
+ if (flags&argument != 0) && (cmd.raw_argument == "") {+ return fmt.Errorf("%s requires an argument\n", cmd.command)+ }
+
+ if (flags&load_url != 0) && (cmd.targeturl == "") {+ return fmt.Errorf("%s: bad url index\n", cmd.command)+ }
+
+ return nil
+}
+
+type dmcmd struct {+ *Hellclient
+}
+
+func (cmd *dmcmd) name() string {+ return "dm"
+}
+
+func (cmd *dmcmd) flags() cmdflag {+ return argument
+}
+
+func (cmd *dmcmd) result(data *cmddata) string {+ hc := cmd.Hellclient
+ if data.raw_argument != "" {+ hc.dispatchStatus(data.raw_argument, "direct")
+ return ""
+ }
+ hc.page = &Page{}+ getter := &BasicStatusGetter{getter: hc.client.GetTimelineDirect}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ fmt.Print(hc.page.String())
+ hc.pause(true)
+ return ""
+}
+
+type profilecmd struct {+ *Hellclient
+ *cmdloader
+}
+
+func (cmd *profilecmd) name() string {+ return "profile"
+}
+
+func (cmd *profilecmd) flags() cmdflag {+ return account | acc_resolve
+}
+
+func (cmd *profilecmd) result(data *cmddata) string {+ hc := cmd.Hellclient
+ account := data.account
+ if account == nil {+ return fmt.Sprintf("Account lookup failed.\n")+ }
+ return fmt.Sprint(hc.formatAccount(account))
+}
+
+type helpcmd struct {+ *Hellclient
+}
+
+func (cmd *helpcmd) name() string {+ return "help"
+}
+
+func (cmd *helpcmd) flags() cmdflag {+ return free
+}
+
+func (cmd *helpcmd) result(data *cmddata) string {+ return fmt.Sprintln(hyphenate(helpString(cmd.configPath)))
+}
+
+type reloadcmd struct {+ *Hellclient
+}
+
+func (cmd *reloadcmd) flags() cmdflag {+ return free
+}
+
+func (cmd *reloadcmd) name() string {+ return "reload"
+}
+
+func (cmd *reloadcmd) result(data *cmddata) string {+ account, _, err := loadConfig()
+ if err != nil {+ return fmt.Sprintf("Error reloading config: %s\n", err)+ }
+ cmd.preferences = &account.Preferences
+ return fmt.Sprintln("Successfully reloaded preferences")+
+}
+
+type detachcmd struct {+ *Hellclient
+}
+
+func (cmd *detachcmd) flags() cmdflag {+ return free
+}
+
+func (cmd *detachcmd) name() string {+ return "detach"
+}
+
+func (cmd *detachcmd) result(data *cmddata) string {+ cmd.attacher.clearAttachments()
+ cmd.prompt.UpdatePrompt()
+ return ""
+}
+
+type attachcmd struct {+ *Hellclient
+}
+
+func (cmd *attachcmd) flags() cmdflag {+ return free
+}
+
+func (cmd *attachcmd) name() string {+ return "attach"
+}
+
+func (cmd *attachcmd) result(data *cmddata) string {+ hc := cmd.Hellclient
+ var err error
+ filename := data.content
+ if filename == "" {+ filename, err = pickFilename(hc.preferences.FilePicker)
+ }
+ if err != nil {+ return fmt.Sprintf("File picking error: %s\n", err)+ }
+ err = hc.attacher.uploadAttachment(filename)
+ if err != nil {+ return fmt.Sprintf("Upload error: %s\n", err)+ }
+ hc.prompt.UpdatePrompt()
+ return ""
+}
+
+type statscmd struct {+ *Hellclient
+}
+
+func (cmd *statscmd) flags() cmdflag {+ return free
+}
+
+func (cmd *statscmd) name() string {+ return "stats"
+}
+
+func (cmd *statscmd) result(data *cmddata) string {+ hc := cmd.Hellclient
+ hc.stats.slock.Lock()
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("API Calls: %d\n", hc.stats.APICalls))+ sb.WriteString(fmt.Sprintf("Statuses Received: %d\n", hc.stats.IncomingStatuses))+ sb.WriteString("Statuses per hour:")+ sb.WriteString(fmt.Sprintf("%.2f\n", float32(hc.stats.IncomingStatuses)/(float32(time.Since(hc.stats.StartedTime))/float32(time.Hour))))+ sb.WriteString(fmt.Sprintf("Started At: %s\n", hc.stats.StartedTime))+ timeSince := time.Since(hc.stats.StartedTime)
+ timeSince = timeSince.Round(time.Second)
+ sb.WriteString(fmt.Sprintf("Runtime: %s\n", timeSince.String()))+ hc.stats.slock.Unlock()
+ return sb.String()
+}
+
+type basiccmd struct {+ hc *Hellclient
+ bname string
+ bflags cmdflag
+ doer func(*cmddata) string
+}
+
+func (cmd *basiccmd) flags() cmdflag {+ return cmd.bflags
+}
+
+func (cmd *basiccmd) name() string {+ return cmd.bname
+}
+
+func (cmd *basiccmd) result(data *cmddata) string {+ return cmd.doer(data)
+}
+
+func (hc *Hellclient) versioncmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "version"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ if buildInfo, ok := debug.ReadBuildInfo(); ok {+ return fmt.Sprintf("%+v\n", buildInfo)+ }
+ return fmt.Sprintf("No version information available.")+ }
+ return cmd
+}
+
+func (hc *Hellclient) prevcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "prev"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ if hc.page != nil {+ hc.page.Prev()
+ return fmt.Sprint(hc.page.String())
+ }
+ return fmt.Sprintf("No page loaded")+ }
+ return cmd
+}
+
+func (hc *Hellclient) nextcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "next"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ if hc.page != nil {+ hc.page.Next()
+ return hc.page.String()
+ }
+ return fmt.Sprintf("No page loaded")+ }
+ return cmd
+}
+
+func (hc *Hellclient) pagecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "page"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ if hc.page != nil {+ return hc.page.String()
+ }
+ return fmt.Sprintf("No page loaded")+ }
+ return cmd
+}
+
+func (hc *Hellclient) bookmarkscmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "bookmarks"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.pause(true)
+ hc.page = &Page{}+ getter := &BasicStatusGetter{getter: hc.client.GetBookmarks}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ return hc.page.String()
+ }
+ return cmd
+}
+
+func (hc *Hellclient) readcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "read"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ defer hc.prompt.UpdatePrompt()
+ notifications, err := hc.GetUnreadNotifications()
+ if err != nil {+ return fmt.Sprintf("%s\n", err)+ }
+ if len(notifications) > 0 {+ err = hc.SetNotificationsRead(notifications[len(notifications)-1].ID)
+ if err != nil {+ return fmt.Sprintf("%s\n", err)+ }
+ }
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) noticecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "notice"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ defer hc.prompt.UpdatePrompt()
+ defer hc.pause(true)
+ notifications, err := hc.GetUnreadNotifications()
+ if len(notifications) > 0 {+ hc.PrintNotifications(notifications)
+ err = hc.SetNotificationsRead(notifications[len(notifications)-1].ID)
+ if err != nil {+ return fmt.Sprintf("%s\n", err)+ }
+ return ""
+ }
+ hc.page = &Page{}+ hc.page.loader = &NotificationPages{hc: hc}+ return hc.page.String()
+ }
+ return cmd
+}
+
+func (hc *Hellclient) pausecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "pause"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.togglepause()
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) resumecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "resume"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.pause(false)
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) replycmd() cmder {+ cmd := &basiccmd{}+ cmd.bname = "reply"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ if data.status != nil {+ hc.dispatchReply(data.content, data.status.SpoilerText, data.status.ID, data.status)
+ }
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) rmcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "rm"
+ cmd.bflags = status | want_recent
+ cmd.doer = func(data *cmddata) string {+ if data.status != nil {+ var err error
+ deletefunc := func(job *GenericJob) {+ err = hc.client.DeleteStatus(context.Background(), data.status.ID)
+ }
+ deleteJob := hc.dispatchFunc(deletefunc)
+ deleteJob.Wait()
+ if err != nil {+ return fmt.Sprintln(err)
+ }
+ }
+ if !data.found_index {+ hc.recentpost = nil
+ }
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) homecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "home"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.page = &Page{}+ getter := &BasicStatusGetter{getter: hc.client.GetTimelineHome}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ hc.pause(true)
+ return hc.page.String()
+
+ }
+ return cmd
+}
+
+func (hc *Hellclient) localcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "local"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.page = &Page{}+ localAPI := func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error) {+ return hc.client.GetTimelinePublic(ctx, true, pg)
+ }
+ getter := &BasicStatusGetter{getter: localAPI}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ hc.pause(true)
+ return hc.page.String()
+
+ }
+ return cmd
+}
+
+func (hc *Hellclient) publiccmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "public"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.page = &Page{}+ localAPI := func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error) {+ return hc.client.GetTimelinePublic(ctx, false, pg)
+ }
+ getter := &BasicStatusGetter{getter: localAPI}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ hc.pause(true)
+ return hc.page.String()
+
+ }
+ return cmd
+}
+
+func (hc *Hellclient) catcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "cat"
+ cmd.bflags = status | templater
+ cmd.doer = func(data *cmddata) string {+ line, _ := data.templater.render("$index $display_name $username_full $content $media_descriptions\n$detail_line")+ return fmt.Sprint(line)
+ }
+ return cmd
+}
+
+//Untested and undocumtend my intance doesn't do this
+//Don't even have the renderer set up here
+func (hc *Hellclient) translatecmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "translate"
+ cmd.bflags = status | templater
+ cmd.doer = func(data *cmddata) string {+ line, _ := data.templater.render("$index $display_name $username_full $content $media_descriptions\n$detail_line")+ return fmt.Sprint(line)
+ translated, err := hc.client.TranslateStatus(context.Background(), data.status.ID)
+ if err != nil {+ return fmt.Sprintf("Translation error: %s\n", err)+
+ }
+ hc.PrintObjectProperties(translated)
+ return ""
+ }
+ return cmd
+}
+
+type likecmd struct {+ *Hellclient
+ islike bool
+}
+
+func (like *likecmd) name() string {+ if like.islike {+ return "like"
+ }
+ return "unlike"
+}
+
+func (like *likecmd) flags() cmdflag {+ return status | templater
+}
+
+func (like *likecmd) result(data *cmddata) string {+ var likefunc func()
+ var err error
+ var verb string
+ hc := like.Hellclient
+ if like.islike {+ likefunc = func() {+ verb = "Favourited"
+ _, err = hc.client.Favourite(context.Background(), data.status.ID)
+ }
+ } else {+ likefunc = func() {+ verb = "Unfavourited"
+ _, err = hc.client.Unfavourite(context.Background(), data.status.ID)
+ }
+ }
+ hc.dispatchAnon(likefunc).Wait()
+ if err != nil {+ return fmt.Sprint("err: %s\n", err)+ }
+ line, _ := data.templater.render(fmt.Sprintf("%s: $standard_status", verb))+ return line
+}
+
+func (hc *Hellclient) markcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "mark"
+ cmd.bflags = status | templater
+ cmd.doer = func(data *cmddata) string {+ var err error
+ markfunc := func() { _, err = hc.client.Bookmark(context.Background(), data.status.ID) }+ hc.dispatchAnon(markfunc).Wait()
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+ line, _ := data.templater.render("Bookmarked: $index $username $content $media_descriptions\n")+ return line
+ }
+ return cmd
+}
+
+func (hc *Hellclient) unmarkcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "unmark"
+ cmd.bflags = status | templater
+ cmd.doer = func(data *cmddata) string {+ var err error
+ var postCopy *mastodon.Status
+ unmarkfunc := func() {+ postCopy, err = hc.client.GetStatus(context.Background(), data.status.ID)
+ }
+ hc.dispatchAnon(unmarkfunc).Wait()
+
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+
+ if !postCopy.Bookmarked.(bool) {+ return "Status not bookmarked.\n"
+ }
+
+ unmarkfunc = func() { _, err = hc.client.Unbookmark(context.Background(), data.status.ID) }+ hc.dispatchAnon(unmarkfunc).Wait()
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+ line, _ := data.templater.render("Unbookmarked: $index $username $content $media_descriptions\n")+ return line
+ }
+ return cmd
+}
+
+func (hc *Hellclient) statusurlcmd(name string, command *string,) cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = name
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ url := fmt.Sprintf("%v/statuses/%v", hc.client.Config.Server, data.status.ID)+ openItemInOS(*command, url)
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) urlcmd(name string, command *string) cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = name
+ cmd.bflags = load_url | status
+ cmd.doer = func(data *cmddata) string {+ openItemInOS(*command, data.targeturl)
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) mediacmd(name string, command *string) cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = name
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ err := hc.previewPostImages(data.status, *command)
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+ return ""
+ }
+ return cmd
+}
+
+//this is using printf fixme
+func (hc *Hellclient) downloadcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "download"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ savePostImages(data.status, hc.preferences.Save_Location)
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) hrtcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "hrt"
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ var account *mastodon.Account
+ if data.reblogger != nil {+ account = &data.reblogger.Account
+ } else {+ account = data.account
+ }
+ relationships, err := hc.client.GetAccountRelationships(context.Background(), []string{string(account.ID)})+ if err != nil {+ return fmt.Sprintf("err loading relationships: %s\n", err)+ }
+ relationship := relationships[0]
+ if !relationship.Following {+ return fmt.Sprintf("can't filter rts from user you don't follow!\n")+ }
+ if relationship.ShowingReblogs {+ _, err := hc.client.AccountFollowDetailed(context.Background(), account.ID, true, relationship.Notifying)
+ if err != nil {+ return "Error updating settings\n"
+ }
+ return fmt.Sprintf("No longer showing RTs from <%s>\n", account.Acct)+ }
+ _, err = hc.client.AccountFollowDetailed(context.Background(), account.ID, false, relationship.Notifying)
+ if err != nil {+ return "Error updating settings\n"
+ }
+ return fmt.Sprintf("Now showing RTs from <%s>\n", account.Acct)+ }
+ return cmd
+}
+
+func (hc *Hellclient) rtcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "rt"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ var status *mastodon.Status
+ var err error
+ rtfunc := func() {+ status, err = hc.client.Reblog(context.Background(), data.status.ID)
+ hc.recentpost = status
+ }
+ hc.dispatchAnon(rtfunc).Wait()
+ hc.printAndIncrement(hc.ctxref, status)
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+ return ""
+ }
+ return cmd
+}
+
+//another printer fixme
+func (hc *Hellclient) parentcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "parent"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ parentfunc := func() {+ if data.status.InReplyToID == nil {+ fmt.Printf("%v doesn't have a parent\n", data.index)+ return
+ }
+ parentStatus, _ := hc.client.GetStatus(context.Background(), mastodon.ID(data.status.InReplyToID.(string)))
+ hc.printAndIncrement(hc.ctxref, parentStatus)
+ return
+ }
+ hc.dispatchAnon(parentfunc).Wait()
+ return ""
+ }
+ return cmd
+}
+
+//another printer fixme
+func (hc *Hellclient) childrencmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "children"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ childfunc := func() {+ context, err := hc.client.GetStatusContext(context.Background(), data.status.ID)
+ if err != nil {+ fmt.Println(err)
+ return
+ }
+ if len(context.Descendants) == 0 {+ fmt.Printf("\"%s\" has no children\n", data.index)+ }
+ for post := range context.Descendants {+ hc.printAndIncrement(hc.ctxref, context.Descendants[post])
+ }
+ return
+ }
+ hc.dispatchAnon(childfunc).Wait()
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) editcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "edit"
+ cmd.bflags = status
+ cmd.doer = func(data *cmddata) string {+ var err error
+ if data.content == "" || data.content == " " {+ if data.status.Account.ID != hc.currentuser.ID {+ return fmt.Sprintf("cannot edit other's statuses!\n")+ }
+ fixedHTML, err := prepareForEdit(data.status)
+ if err != nil {+ return fmt.Sprintf("Error loading post HTML: %s\n", err)+ }
+ hc.rl.SetDefault(fmt.Sprintf("/edit %v %v", data.index, html2text.HTML2TextWithOptions(fixedHTML, html2text.WithUnixLineBreaks())))+ return ""
+ }
+ var MediaIDs []mastodon.ID
+ for _, media := range data.status.MediaAttachments {+ MediaIDs = append(MediaIDs, media.ID)
+ }
+ toot := &mastodon.Toot{+ Status: data.content,
+ MediaIDs: MediaIDs,
+ Sensitive: data.status.Sensitive,
+ SpoilerText: data.status.SpoilerText,
+ Visibility: data.status.Visibility,
+ Language: data.status.Language,
+ }
+ if data.status.InReplyToID != nil {+ id := mastodon.ID(data.status.InReplyToID.(string))
+ toot.InReplyToID = id
+ }
+ editfunc := func() {+ _, err = hc.client.UpdateStatus(context.Background(), toot, data.status.ID)
+ }
+ hc.dispatchAnon(editfunc).Wait()
+ if err != nil {+ return fmt.Sprintf("err: %s\n", err)+ }
+ return ""
+ }
+ return cmd
+}
+
+func (hc *Hellclient) threadcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "thread"
+ cmd.bflags = free
+ cmd.doer = func(data *cmddata) string {+ hc.pause(true)
+ hc.page = &Page{disablereverse: true}+ getter := &ThreadStatusGetter{target: data.status, client: hc.client}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ return hc.page.String()
+ }
+ return cmd
+}
+
+func (hc *Hellclient) pinnedcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "pinned"
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ hc.pause(true)
+ hc.page = &Page{}+ hc.page.itembuffer = new([]PageItem)
+ getter := &PinnedStatusGetter{client: hc.client, ID: data.account.ID}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ return hc.page.String()
+ }
+ return cmd
+}
+
+func (hc *Hellclient) accountcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "account"
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ hc.pause(true)
+ hc.page = &Page{}+ hc.page.itembuffer = new([]PageItem)
+ getter := &AccountStatusGetter{client: hc.client, ID: data.account.ID}+ hc.page.loader = &StatusPages{hc: hc, getter: getter}+ return hc.page.String()
+ }
+ return cmd
+}
+
+func (hc *Hellclient) followcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "follow"
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ account := data.account
+ var err error
+ var relationship *mastodon.Relationship
+ followfunc := func(job *GenericJob) { relationship, err = hc.client.AccountFollow(context.Background(), account.ID) }+ followjob := hc.dispatchFunc(followfunc)
+ followjob.Wait()
+ if err != nil {+ return fmt.Sprintf("Error requesting follow: %s\n", err)+ }
+ if relationship.Following {+ return fmt.Sprintf("Successfully followed %s\n", account.Acct)+ }
+ if relationship.Requested {+ return fmt.Sprintf("Follow request sent to %s\n", account.Acct)+ }
+ return "No error but no follow or request returned in response.\n"
+ }
+ return cmd
+}
+
+func (hc *Hellclient) unfollowcmd() cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = "unfollow"
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ var relationship *mastodon.Relationship
+ account := data.account
+ var err error
+ unfollowfunc := func(job *GenericJob) { relationship, err = hc.client.AccountUnfollow(context.Background(), account.ID) }+ unfollowjob := hc.dispatchFunc(unfollowfunc)
+ unfollowjob.Wait()
+ if err != nil {+ return fmt.Sprintf("Error unfollowing account: %s\n", err)+ }
+ if !relationship.Following {+ return fmt.Sprintf("Successfully unfollowed %s\n", data.index)+ }
+ return "No error but account is still followed in response\n"
+ }
+ return cmd
+}
+
+func (hc *Hellclient) filtercmd(name string, filter bool) cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = name
+ cmd.bflags = status | templater
+ cmd.doer = func(data *cmddata) string {+ var err error
+ if filter {+ _, err = hc.filterStatus(data.status)
+ if err != nil {+ return fmt.Sprintf("Error filtering post: %v\n", err)+ }
+ url := fmt.Sprintf("%v/statuses/%v", hc.client.Config.Server, data.status.ID)+ return fmt.Sprintf("Filtered %v\n", url)+ }
+ _, err = hc.unfilterStatus(data.status)
+ if err != nil {+ return fmt.Sprintf("Error unfiltering post: %v\n", err)+ }
+ line, _ := data.templater.render("Unfiltered: $standard_or_subject\n")+ return line
+ }
+ return cmd
+}
+
+func (hc *Hellclient) blockcmd(name string, block bool) cmder {+ cmd := &basiccmd{}+ cmd.hc = hc
+ cmd.bname = name
+ cmd.bflags = account | acc_resolve
+ cmd.doer = func(data *cmddata) string {+ var err error
+ var result string
+ var blockfunc func()
+ if block {+ blockfunc = func() {+ _, err = hc.client.AccountBlock(context.Background(), data.account.ID)
+ if err != nil {+ result = fmt.Sprintf("Error blocking account: %s.\n", data.account.Acct)+ return
+ }
+ result = fmt.Sprintf("Account blocked: %s\n", data.account.Acct)+ return
+ }
+ } else {+ blockfunc = func() {+ _, err := hc.client.AccountUnblock(context.Background(), data.account.ID)
+ if err != nil {+ result = fmt.Sprintf("Error unblocking account: %s.\n", data.account.Acct)+ }
+ result = fmt.Sprintf("Account unblocked: %s\n", data.account.Acct)+ return
+ }
+ }
+ hc.dispatchAnon(blockfunc).Wait()
+ return result
+ }
+ return cmd
+}
+
+// Commmands are lazy evaluated in this order
+// Single/two letter matches need to match the most common commands
+func (hc *Hellclient) newCmdArray() []cmder {+ cmdarray := []cmder{+ &likecmd{hc, true},+ &likecmd{hc, false},+ hc.markcmd(),
+ hc.unmarkcmd(),
+ hc.translatecmd(),
+ hc.catcmd(),
+ hc.childrencmd(),
+ hc.replycmd(),
+ hc.rtcmd(),
+ hc.rmcmd(),
+ &reloadcmd{hc},+ &profilecmd{hc, hc.cmdload},+ hc.publiccmd(),
+ hc.pagecmd(),
+ hc.parentcmd(),
+ hc.pinnedcmd(),
+ &dmcmd{hc},+ hc.downloadcmd(),
+ &helpcmd{hc},+ &detachcmd{hc},+ hc.accountcmd(),
+ &attachcmd{hc},+ &statscmd{hc},+ hc.versioncmd(),
+ hc.prevcmd(),
+ hc.nextcmd(),
+ hc.bookmarkscmd(),
+ hc.blockcmd("block", true),+ hc.readcmd(),
+ hc.noticecmd(),
+ hc.pausecmd(),
+ hc.resumecmd(),
+ hc.homecmd(),
+ hc.hrtcmd(),
+ hc.localcmd(),
+ hc.statusurlcmd("open", &hc.preferences.Browser),+ hc.urlcmd("url", &hc.preferences.Browser),+ hc.filtercmd("ufpost", false),+ hc.unfollowcmd(),
+ hc.blockcmd("unblock", false),+ hc.urlcmd("play", &hc.preferences.MediaPlayer),+ hc.mediacmd("view", &hc.preferences.ImageViewer),+ hc.mediacmd("import", &hc.preferences.MediaImport),+ hc.editcmd(),
+ hc.threadcmd(),
+ hc.followcmd(),
+ hc.filtercmd("fpost", true),+
+ }
+ return cmdarray
}
--- a/hellclient.go
+++ b/hellclient.go
@@ -34,6 +34,7 @@
preferences *Hellprefs
multiLineMode bool
configPath string
+ cmdload *cmdloader
//Global status map for status indexes
//Needs to be converted to a postref struct
@@ -126,6 +127,10 @@
hc.prompt.UpdatePrompt()
//start up post dispatcher
go hc.clientDispatch()
+
+ //command processor
+ hc.cmdload = &cmdloader{hc: &hc}+ hc.cmdload.init(hc.newCmdArray())
markers, err := hc.client.GetTimelineMarkers(context.Background(), []string{"home"}) if err != nil {--- a/main.go
+++ b/main.go
@@ -1,15 +1,7 @@
package main
import (
- "context"
"fmt"
- "runtime/debug"
- "strconv"
- "strings"
- "time"
-
- mastodon "codeberg.org/penny64/hellclient-go-mastodon"
- "github.com/k3a/html2text"
)
func main() {@@ -22,21 +14,15 @@
rl := hc.rl
client := *hc.client
enablePipeHack(rl)
+ go StreamHomeTimeline(&client, hc.homeMap, hc)
- homeMap := hc.homeMap
- debugMap := hc.debugMap
- lastindex := ""
- recentpost := &hc.recentpost
- var lastaccount *mastodon.Account
-
-
- go StreamHomeTimeline(&client, homeMap, hc)
-
for { func() {line, err := rl.Readline()
+ hc.lock()
+ defer hc.unlock()
- command, arguments, found := processInput(line)
+ command, arguments, _ := processInput(line, []string{})//empty line
if command == "" && arguments == "" && err == nil {@@ -54,26 +40,21 @@
return
}
+ result, err := hc.cmdload.run(line)
+ if err != nil {+ fmt.Print(err)
+ }
+ fmt.Print(result)
+ return
+
if !found { fmt.Printf("Command not found: \"%s\"\n", command)return
}
- hc.lock()
- defer hc.unlock()
-
index, content, _ := strings.Cut(arguments, " ")
-
-
- // "." refers to most recently acted on status
- // but if we have an account set, we don't want to set an index!
- if index == "." && lastaccount == nil {- index = lastindex
- }
-
postItem, postOK := homeMap[index]
- debugItem := debugMap[index]
//Wether we got a post index or not
foundindex := false
@@ -81,21 +62,25 @@
if postOK {foundindex = true
lastindex = index
- lastaccount = nil
} else {postItem, postOK = homeMap[lastindex]
}
-
-
- var reblogger *mastodon.Status
+
//Okay now see if the post we end up with is a reblog
if postOK { if postItem.Reblog != nil {- reblogger = postItem
postItem = postItem.Reblog
}
}
+ cmdctx, _ := hc.cmdload.processLine(line)
+ hc.PrintObjectProperties(cmdctx.account)
+ hc.PrintObjectProperties(cmdctx.status)
+ hc.PrintObjectProperties(cmdctx.reblogger)
+ fmt.Println(cmdctx.raw_argument)
+ fmt.Println(cmdctx.command)
+ fmt.Println(cmdctx.content)
+ fmt.Println(cmdctx.index)
accByNameOrRef := func() (account *mastodon.Account) { if lastaccount != nil {@@ -112,569 +97,11 @@
lastaccount = account
return
}
-
- //Contextual commands that need to handle their own requirements
- switch command {- case "detach":
- hc.attacher.clearAttachments()
- hc.prompt.UpdatePrompt()
- return
- case "attach":
- if arguments == "" {- arguments, err = pickFilename(hc.preferences.FilePicker)
- }
- if err != nil {- fmt.Printf("File picking error: %s\n", err)- return
- }
- err := hc.attacher.uploadAttachment(arguments)
- if err != nil {- fmt.Printf("Upload error: %s\n", err)- return
- }
- hc.prompt.UpdatePrompt()
- return
- case "help":
- fmt.Println(hyphenate(helpString(hc.configPath)))
- return
- case "reload":
- account, _, err := loadConfig()
- if err != nil {- fmt.Printf("Error reloading config: %s\n", err)- return
- }
- fmt.Println("Successfully reloaded preferences")- hc.preferences = &account.Preferences
- return
- case "stats":
- hc.stats.slock.Lock()
- var sb strings.Builder
- sb.WriteString(fmt.Sprintf("API Calls: %d\n", hc.stats.APICalls))- sb.WriteString(fmt.Sprintf("Statuses Received: %d\n", hc.stats.IncomingStatuses))- sb.WriteString("Statuses per hour:")- sb.WriteString(fmt.Sprintf("%.2f\n", float32(hc.stats.IncomingStatuses)/(float32(time.Since(hc.stats.StartedTime))/float32(time.Hour))))- sb.WriteString(fmt.Sprintf("Started At: %s\n", hc.stats.StartedTime))- timeSince := time.Since(hc.stats.StartedTime)
- timeSince = timeSince.Round(time.Second)
- sb.WriteString(fmt.Sprintf("Runtime: %s\n", timeSince.String()))- fmt.Print(sb.String())
- hc.stats.slock.Unlock()
- return
- case "version":
- if buildInfo, ok := debug.ReadBuildInfo(); ok {- fmt.Printf("%+v\n", buildInfo)- return
- }
- fmt.Printf("No version information available.")- return
- case "prev":
- if hc.page != nil {- hc.page.Prev()
- fmt.Print(hc.page.String())
- }
- return
- case "next":
- if hc.page != nil {- hc.page.Next()
- fmt.Print(hc.page.String())
- }
- return
- case "page":
- if hc.page != nil {- fmt.Print(hc.page.String())
- }
- return
- case "bookmarks":
- hc.pause(true)
- hc.page = &Page{}- getter := &BasicStatusGetter{getter: hc.client.GetBookmarks}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- return
- case "likes":
- hc.pause(true)
- hc.page = &Page{}- getter := &BasicStatusGetter{getter: hc.client.GetFavourites}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- return
- case "read":
- defer hc.prompt.UpdatePrompt()
- notifications, err := hc.GetUnreadNotifications()
- if err != nil {- fmt.Print(err)
- }
- if len(notifications) > 0 {- err = hc.SetNotificationsRead(notifications[len(notifications)-1].ID)
- if err != nil {- fmt.Print(err)
- }
- }
- return
- case "notice":
- defer hc.prompt.UpdatePrompt()
- defer hc.pause(true)
- notifications, err := hc.GetUnreadNotifications()
- if len(notifications) > 0 {- hc.PrintNotifications(notifications)
- err = hc.SetNotificationsRead(notifications[len(notifications)-1].ID)
- if err != nil {- fmt.Print(err)
- }
- return
- }
- hc.page = &Page{}- hc.page.loader = &NotificationPages{hc: hc}- fmt.Print(hc.page.String())
- if err != nil {- fmt.Print(err)
- }
- return
- case "pause":
- hc.togglepause()
- return
- case "resume":
- hc.pause(false)
- return
- case "rm":
- if !foundindex && *recentpost != nil {- deletefunc := func(job *GenericJob) {- err = client.DeleteStatus(context.Background(), (*recentpost).ID)
- }
- deleteJob := hc.dispatchFunc(deletefunc)
- deleteJob.Wait()
- if err != nil {- fmt.Println(err)
- }
- *recentpost = nil
- return
- }
- if !postOK {- fmt.Println("No recent status to delete or post index not valid")- return
- }
- deletefunc := func(job *GenericJob) {- err = client.DeleteStatus(context.Background(), (*postItem).ID)
- }
- deleteJob := hc.dispatchFunc(deletefunc)
- deleteJob.Wait()
- if err != nil {- fmt.Println(err)
- }
- return
- case "dm":
- if arguments != "" {- hc.dispatchStatus(arguments, "direct")
- return
- }
- hc.page = &Page{}- getter := &BasicStatusGetter{getter: hc.client.GetTimelineDirect}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- hc.pause(true)
- return
- case "home":
- hc.page = &Page{}- getter := &BasicStatusGetter{getter: hc.client.GetTimelineHome}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- hc.pause(true)
- return
- case "local":
- hc.page = &Page{}- localAPI := func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error) {- return hc.client.GetTimelinePublic(ctx, true, pg)
- }
- getter := &BasicStatusGetter{getter: localAPI}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- hc.pause(true)
- return
- case "public":
- hc.page = &Page{}- localAPI := func(ctx context.Context, pg *mastodon.Pagination) ([]*mastodon.Status, error) {- return hc.client.GetTimelinePublic(ctx, false, pg)
- }
- getter := &BasicStatusGetter{getter: localAPI}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- hc.pause(true)
- return
- }
-
- if arguments == "" && !postOK && lastaccount == nil {- fmt.Printf("%v requires an argument\n", command)- return
- }
-
- //Commands that don't take an index
- switch command {- }
-
- //Commands that accept debug indexes
- switch command {- case "examine":
- if foundindex {- hc.PrintObjectProperties(postItem)
- return
- }
- hc.PrintObjectProperties(debugItem)
- return
- }
formatter := &StatusFormatter{prefs: hc.preferences, status: postItem, postContext: hc.ctxref, localindex: index}templater := newStatusTemplateRenderer(formatter)
//Commands require status indexes
switch command {- case "cat":
- if !postOK {- fmt.Println("cat: no valid status")- return
- }
- if index == "" {- index = lastindex
- }
- line, _ := templater.render("$index $display_name $username_full $content $media_descriptions\n$detail_line")- fmt.Print(line)
- return
- case "translate":
- if !postOK {- fmt.Println("translate: no valid status")- return
- }
- if index == "" {- index = lastindex
- }
- translated, err := client.TranslateStatus(context.Background(), postItem.ID)
- if err != nil {- fmt.Printf("Translation error: %s\n", err)- return
- }
- hc.PrintObjectProperties(translated)
- case "like":
- likefunc := func() {- _, err = client.Favourite(context.Background(), postItem.ID)
- }
- hc.dispatchAnon(likefunc).Wait()
- if err != nil {- printMastodonErr(err)
- } else {- line, _ := templater.render("Favourited: $standard_status")- fmt.Print(line)
- }
- return
- case "unlike":
- likefunc := func() {- _, err = client.Unfavourite(context.Background(), postItem.ID)
- }
- hc.dispatchAnon(likefunc).Wait()
- if err != nil {- printMastodonErr(err)
- } else {- line, _ := templater.render("Unfavourited: $standard_status")- fmt.Print(line)
- }
- return
- case "mark":
- markfunc := func() { _, err = client.Bookmark(context.Background(), postItem.ID) }- hc.dispatchAnon(markfunc).Wait()
- if err != nil {- printMastodonErr(err)
- } else {- line, _ := templater.render("Bookmarked: $index $username $content $media_descriptions\n")- fmt.Print(line)
- }
- return
- case "unmark":
- var postCopy *mastodon.Status
- unmarkfunc := func() {- postCopy, err = client.GetStatus(context.Background(), postItem.ID)
- }
- hc.dispatchAnon(unmarkfunc).Wait()
- if err != nil {- fmt.Printf("Error removing bookmark: %s\n", err)- return
- }
- if !postCopy.Bookmarked.(bool) {- fmt.Printf("Post not bookmarked.\n")- return
- }
- hc.dispatchAnon(func() { _, err = client.Unbookmark(context.Background(), postItem.ID) }).Wait()- if err != nil {- printMastodonErr(err)
- } else {- line, _ := templater.render("Unbookmarked: $index $username $content $media_descriptions\n")- fmt.Print(line)
- }
- return
- case "open":
- url := fmt.Sprintf("%v/statuses/%v", client.Config.Server, postItem.ID)- openItemInOS(hc.preferences.Browser, url)
- return
- case "url":
- _, indexstr, _ := strings.Cut(arguments, " ")
- urlindex, err := strconv.Atoi(indexstr)
- if err != nil {- urlindex = 1
- }
- if urlindex > len(hc.homeref.urlmap[index]) {- fmt.Printf("Bad url index\n")- return
- }
- openItemInOS(hc.preferences.Browser, hc.homeref.urlmap[index][urlindex-1])
- return
- case "play":
- _, indexstr, _ := strings.Cut(arguments, " ")
- urlindex, err := strconv.Atoi(indexstr)
- if err != nil {- urlindex = 1
- }
- if urlindex > len(hc.homeref.urlmap[index]) {- fmt.Printf("Bad url index\n")- return
- }
- openItemInOS(hc.preferences.MediaPlayer, hc.homeref.urlmap[index][urlindex-1])
- return
- case "view":
- err := hc.previewPostImages(postItem, hc.preferences.ImageViewer)
- if err != nil {- fmt.Printf("Image preview failed: %v\n", err)- }
- return
- case "import":
- err := hc.previewPostImages(postItem, hc.preferences.MediaImport)
- if err != nil {- fmt.Printf("Image preview failed: %v\n", err)- }
- return
- case "download":
- savePostImages(postItem, hc.preferences.Save_Location)
- return
- case "hrt":
- // We want to filter RTs from the RTer
- if reblogger != nil {- postItem = reblogger
- }
- account := accByNameOrRef()
- if account == nil {- fmt.Printf("Account lookup failed.\n")- return
- }
- relationships, err := hc.client.GetAccountRelationships(context.Background(), []string{string(account.ID)})- if err != nil {- fmt.Printf("Error loading relationship.\n")- return
- }
- relationship := relationships[0]
- if !relationship.Following {- fmt.Printf("can't filter rts from user you don't follow!\n")- return
- }
- if relationship.ShowingReblogs {- _, err := hc.client.AccountFollowDetailed(context.Background(), account.ID, true, relationship.Notifying)
- if err != nil {- fmt.Printf("Error updating settings\n")- return
- }
- fmt.Printf("No longer showing RTs from <%s>\n", account.Acct)- return
- }
- //Turn them back on if they were off!
- _, err = hc.client.AccountFollowDetailed(context.Background(), account.ID, false, relationship.Notifying)
- if err != nil {- fmt.Printf("Error updating settings\n")- return
- }
- fmt.Printf("Now showing RTs from <%s>\n", account.Acct)- return
- case "rt":
- rtfunc := func() {- rtStatus, err := client.Reblog(context.Background(), postItem.ID)
- if err != nil {- fmt.Println(err)
- return
- }
- *recentpost = rtStatus
- hc.printAndIncrement(hc.ctxref, rtStatus)
- return
- }
- hc.dispatchAnon(rtfunc).Wait()
- return
- case "parent":
- parentfunc := func() {- if postItem.InReplyToID == nil {- fmt.Printf("%v doesn't have a parent\n", index)- return
- }
- parentStatus, _ := client.GetStatus(context.Background(), mastodon.ID(postItem.InReplyToID.(string)))
- hc.printAndIncrement(hc.ctxref, parentStatus)
- return
- }
- hc.dispatchAnon(parentfunc).Wait()
- return
- case "children":
- childfunc := func() {- context, err := client.GetStatusContext(context.Background(), postItem.ID)
- if err != nil {- fmt.Println(err)
- return
- }
- if len(context.Descendants) == 0 {- fmt.Printf("\"%s\" has no children\n", index)- }
- for post := range context.Descendants {- hc.printAndIncrement(hc.ctxref, context.Descendants[post])
- }
- return
- }
- hc.dispatchAnon(childfunc).Wait()
- return
- case "edit":
- if content == "" || content == " " {- if (postItem == nil) || postItem.Account.ID != hc.currentuser.ID {- fmt.Printf("cannot edit other's statuses!\n")- return
- }
- fixedHTML, err := prepareForEdit(postItem)
- if err != nil {- fmt.Printf("Error loading post HTML: %s\n", err)- return
- }
- rl.SetDefault(fmt.Sprintf("/edit %v %v", index, html2text.HTML2TextWithOptions(fixedHTML, html2text.WithUnixLineBreaks())))- return
- }
- var MediaIDs []mastodon.ID
- for _, media := range postItem.MediaAttachments {- MediaIDs = append(MediaIDs, media.ID)
- }
- toot := &mastodon.Toot{- Status: content,
- MediaIDs: MediaIDs,
- Sensitive: postItem.Sensitive,
- SpoilerText: postItem.SpoilerText,
- Visibility: postItem.Visibility,
- Language: postItem.Language,
- }
- if postItem.InReplyToID != nil {- id := mastodon.ID(postItem.InReplyToID.(string))
- toot.InReplyToID = id
- }
- editfunc := func() {- _, err = client.UpdateStatus(context.Background(), toot, postItem.ID)
- if err != nil {- fmt.Println(err)
- return
- }
- }
- hc.dispatchAnon(editfunc).Wait()
- return
- case "thread":
- hc.pause(true)
- hc.page = &Page{disablereverse: true}- getter := &ThreadStatusGetter{target: postItem, client: hc.client}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- return
- case "pinned":
- var account *mastodon.Account
- if foundindex || index == "" {- account = &postItem.Account
- } else {- account = hc.resolveAccount(index)
- if account == nil {- return
- }
- }
-
- hc.pause(true)
- hc.page = &Page{}- hc.page.itembuffer = new([]PageItem)
- getter := &PinnedStatusGetter{client: hc.client, ID: account.ID}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- return
- case "profile":
- account := accByNameOrRef()
- if account == nil {- fmt.Printf("Account lookup failed.\n")- return
- }
- fmt.Print(hc.formatAccount(account))
- return
- case "account":
- account := accByNameOrRef()
- if account == nil {- fmt.Printf("Account lookup failed.\n")- return
- }
- hc.pause(true)
- hc.page = &Page{}- hc.page.itembuffer = new([]PageItem)
- getter := &AccountStatusGetter{client: hc.client, ID: account.ID}- hc.page.loader = &StatusPages{hc: hc, getter: getter}- fmt.Print(hc.page.String())
- return
- case "unfollow":
- account := accByNameOrRef()
- if account == nil {- fmt.Printf("Account lookup failed.")- return
- }
- var relationship *mastodon.Relationship
- unfollowfunc := func(job *GenericJob) { relationship, err = hc.client.AccountUnfollow(context.Background(), account.ID) }- unfollowjob := hc.dispatchFunc(unfollowfunc)
- unfollowjob.Wait()
- if err != nil {- fmt.Printf("Error unfollowing account: %s\n", err)- return
- }
- if !relationship.Following {- fmt.Printf("Successfully unfollowed %s\n", index)- return
- }
- fmt.Printf("Unknown failure, account is still followed\n")- return
-
- case "follow":
- account := accByNameOrRef()
- if account == nil {- fmt.Printf("Account lookup failed.\n")- return
- }
- var relationship *mastodon.Relationship
- followfunc := func(job *GenericJob) { relationship, err = hc.client.AccountFollow(context.Background(), account.ID) }- followjob := hc.dispatchFunc(followfunc)
- followjob.Wait()
- if err != nil {- fmt.Printf("Error requesting follow: %s\n", err)- return
- }
- if relationship.Following {- fmt.Printf("Successfully followed %s\n", account.Acct)- return
- }
- if relationship.Requested {- fmt.Printf("Follow request sent to %s\n", account.Acct)- return
- }
- case "fpost":
- _, err := hc.filterStatus(postItem)
- if err != nil {- fmt.Printf("Error filtering post: %v\n", err)- return
- }
- url := fmt.Sprintf("%v/statuses/%v", client.Config.Server, postItem.ID)- fmt.Printf("Filtered %v\n", url)- return
- case "ufpost":
- _, err := hc.unfilterStatus(postItem)
- if err != nil {- fmt.Printf("Error unfiltering post: %v\n", err)- return
- }
- line, _ := templater.render("Unfiltered: %s> $standard_or_subject\n")- fmt.Print(line)
- return
case "block":
var account *mastodon.Account
if foundindex || index == "" {@@ -724,13 +151,6 @@
fmt.Printf("\"%v\" requires an argument\n", command)return
}
-
- switch command {- case "reply":
- hc.dispatchReply(content, postItem.SpoilerText, postItem.ID, postItem)
- return
- }
-
}()
}
}
--
⑨