ref: 95adfb5f6d502e0c75f07b181c74e97033ef66e2
dir: /commands.go/
package main
import (
"fmt"
"runtime/debug"
"strings"
"time"
"context"
"strconv"
"github.com/k3a/html2text"
mastodon "codeberg.org/penny64/hellclient-go-mastodon"
)
func processInput(input string, commands []string) (command string, arguments string, found bool) {
if input == "" {
command = ""
arguments = ""
return
}
if input[0] != '/' {
command = ""
arguments = input
return
}
inputcommand, _, hasargument := strings.Cut(input[1:], " ")
if !hasargument {
inputcommand = input[1:]
}
for _, choice := range commands {
if strings.HasPrefix(choice, inputcommand) {
command = choice
_, arguments, _ = strings.Cut(input, " ")
found = true
break
}
}
if command == "" {
command = inputcommand
}
return
}
func pullParameter(input string) (parameter string, remainder string) {
if strings.HasPrefix(input, "?") {
parameter, _, _ = strings.Cut(input, " ")
if len(parameter) == len(input) {
remainder = ""
parameter = input
} else {
remainder = input[len(parameter)+1:]
}
} else {
return "", input
}
prefix, _, cut := strings.Cut(parameter, "\"")
if cut {
quote, _, _ := strings.Cut(input[len(prefix)+1:], "\"")
parameter = prefix + quote
if len(parameter)+3 <= len(input) {
remainder = input[len(parameter)+3:]
} else {
remainder = input[len(parameter)+2:]
}
}
return
}
func splitParameter(input string) (parameter []string) {
if len(input) < 2 {
//nothing here
return nil
}
input = input[1:]
key, value, _ := strings.Cut(input, "=")
return []string{key, value}
}
func extractInputParameters(input string) (remainder string, parameters [][]string) {
parameter := " "
remainder = input
for parameter != "" {
parameter, remainder = pullParameter(remainder)
if parameter != " " && parameter != "" {
parameters = append(parameters, splitParameter(parameter))
}
}
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 {
if data.account != nil {
if data.status.Account.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
}
if index == "@" {
loader.lastindex = index
loader.lastaccount = loader.hc.currentuser
}
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]
content = arguments
}
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 free
}
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}
hc.pause(true)
return hc.page.Pages(1)
}
type likescmd struct {
*Hellclient
}
func (cmd *likescmd) name() string {
return "lp"
}
func (cmd *likescmd) flags() cmdflag {
return free
}
func (cmd *likescmd) result(data *cmddata) string {
hc := cmd.Hellclient
hc.page = &Page{}
getter := &BasicStatusGetter{getter: hc.client.GetFavourites}
hc.page.loader = &StatusPages{hc: hc, getter: getter}
hc.pause(true)
return hc.page.Pages(1)
}
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
fmt.Println(data.raw_argument)
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()
num, _ := strconv.Atoi(data.content)
num = -num
return fmt.Sprint(hc.page.Pages(num))
}
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()
num, _ := strconv.Atoi(data.content)
return hc.page.Pages(num)
}
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.Pages(1)
}
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.Pages(1)
}
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.Pages(1)
}
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.Pages(1)
}
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.Pages(1)
}
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.Pages(1)
}
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 {
replyLine := ""
if data.status.InReplyToAccountID != nil {
var account *mastodon.Account
var err error
getaccount := func() {
ID := mastodon.ID(data.status.InReplyToAccountID.(string))
account, err = hc.client.GetAccount(context.Background(), ID)
}
hc.dispatchAnon(getaccount).Wait()
if err == nil {
replyLine = fmt.Sprintf("Reply to %s\n", account.Acct)
}
}
renderLine := fmt.Sprintf("$index $display_name $username_full $content $media_descriptions\n%s$detail_line", replyLine)
line, _ := data.templater.render(renderLine)
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 {
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.Pages(1)
}
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.Pages(1)
}
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.Pages(1)
}
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
}
//self printer fixme
func (hc *Hellclient) examinecmd() cmder {
cmd := &basiccmd{}
cmd.hc = hc
cmd.bname = "examine"
cmd.bflags = free
cmd.doer = func(data *cmddata) string {
if data.found_index || data.dot_index {
if data.status != nil {
hc.PrintObjectProperties(data.status)
return ""
}
hc.PrintObjectProperties(data.account)
return ""
}
debugItem, debugOK := hc.debugMap[data.index]
if debugOK {
hc.PrintObjectProperties(debugItem)
}
return ""
}
return cmd
}
func (hc *Hellclient) votecmd() cmder {
cmd := &basiccmd{}
cmd.hc = hc
cmd.bname = "vote"
cmd.bflags = status | templater
cmd.doer = func(data *cmddata) string {
if data.status.Poll == nil {
return "Status does not contain a poll.\n"
}
votes := strings.Fields(data.content)
var voteints []int
for _, vote := range votes {
voter := int(rune(vote[0]))
voter = voter - 97
voteints = append(voteints, voter)
}
var poll *mastodon.Poll
var err error
votefunc := func() {
poll, err = hc.client.PollVote(context.Background(), data.status.Poll.ID, voteints...)
}
hc.dispatchAnon(votefunc).Wait()
if err != nil {
return fmt.Sprintf("Err: %s\n", err)
}
data.status.Poll = poll
line, _ := data.templater.render("$standard_status")
return line
}
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},
&likescmd{hc}, //lp
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),
hc.examinecmd(),
hc.votecmd(),
}
return cmdarray
}