shithub: hell

ref: ae200213eaf1b3c49bc7a119bbc95f744685c8c6
dir: /hellclient.go/

View raw version
package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"time"

	"codeberg.org/penny64/hellclient-go-mastodon"
	"github.com/ergochat/readline"
)

var (
	ErrInterrupt = readline.ErrInterrupt
	EOF          = io.EOF
)

type postref struct {
	prefix  string
	ref     string
	postmap map[string]*mastodon.Status
}

type Hellclient struct {
	//if you're gonna touch or read anything here lock the mutex with hc.lock()
	isPaused      bool
	rl            *readline.Instance
	client        *mastodon.Client
	currentuser   *mastodon.Account
	dispatch      chan *mastodon.Toot
	jobdispatch   chan Job
	block         sync.Mutex
	recentpost    *mastodon.Status
	preferences   *Hellprefs
	multiLineMode bool

	//Global status map for status indexes
	//Needs to be converted to a postref struct
	homeMap map[string]*mastodon.Status
	homeref *postref
	//Contextual indexes for commands
	ctxref       *postref
	urlMap       map[string][]string
	debugMap     map[string]interface{}
	actionBuffer []func()

	//pointer to our current page item
	page  *Page
	stats *Stats
}

type Stats struct {
	slock            sync.Mutex
	APICalls         int64
	IncomingStatuses int64
	StartedTime      time.Time
}

// Use this to make private versions of runes to stop default behavior
func toPUA(r rune) rune {
	const (
		puaStart = 0xE000
		puaEnd   = 0xF8FF
		puaSize  = puaEnd - puaStart + 1
	)
	return rune(int32(puaStart) + (int32(r) % int32(puaSize)))
}

func NewHellclient() (*Hellclient, error) {
	var hc Hellclient
	config := &readline.Config{
		Prompt: "Hell> ",
		FuncFilterInputRune: func(r rune) (rune, bool) {
			if r == readline.CharCtrlJ {
				hc.multiLineMode = !hc.multiLineMode
				hc.updatePrompt()
				return r, false
			}
			if r == readline.CharInterrupt {
				return toPUA(r), true
			}
			if r == readline.CharEnter && hc.multiLineMode {
				return toPUA(r), true
			}
			return r, true
		},
		Listener: func(line []rune, pos int, key rune) ([]rune, int, bool) {
			if key == toPUA(readline.CharEnter) && hc.multiLineMode {
				// handle multi-line input
				line = line[:len(line)-1]
				line = append(line, '\n')
				return line, pos, true
			}

			//If we get an interupt just clear the line if it's not empty
			if key == toPUA(readline.CharInterrupt) {
				if len(line) > 1 {
					return nil, 0, true
				}
				hc.rl.Close()
				os.Exit(0)
			}
			return nil, 0, false
		},
	}
	rl, err := readline.NewEx(config)
	if err != nil {
		return nil, err
	}

	account, err := loadConfig()
	if err != nil {
		return nil, err
	}
	client := initClient(account)
	currentuser, err := client.GetAccountCurrentUser(context.Background())
	if err != nil {
		return nil, err
	}

	dispatch := make(chan *mastodon.Toot, 15)
	jobdispatch := make(chan Job)

	defer func() {
		//Got some stuff to do when we're done
		hc.updatePrompt()
		//start up post dispatcher
		go hc.clientDispatch()

		markers, err := hc.client.GetTimelineMarkers(context.Background(), []string{"home"})
		if err != nil {
			return
		}

		initReferenceSystem()

		lastReadID := markers["home"].LastID
		statuses, err := hc.GetStatusesSince(lastReadID, hc.client.GetTimelineHome)
		if err != nil {
			return
		}
		if len(statuses) > 0 {
			hc.updateReadMarker(&statuses[len(statuses)-1].ID, "home")
		}

		for _, status := range statuses {
			hc.printAndIncrement(hc.ctxref, status)
		}
		//Record start time when everything is set up and connected
		hc.stats.StartedTime = time.Now()
	}()

	homeMap := make(map[string]*mastodon.Status)

	ctxref := &postref{
		prefix:  "?",
		ref:     "a",
		postmap: homeMap,
	}

	homeref := &postref{
		ref:     "a",
		postmap: homeMap,
	}

	debugMap := make(map[string]interface{})
	urlMap := make(map[string][]string)
	prefs := &account.Preferences
	hc = Hellclient{rl: rl,
		homeMap:     homeMap,
		ctxref:      ctxref,
		homeref:     homeref,
		debugMap:    debugMap,
		isPaused:    false,
		urlMap:      urlMap,
		client:      client,
		currentuser: currentuser,
		dispatch:    dispatch,
		jobdispatch: jobdispatch,
		preferences: prefs,
		stats:       &Stats{},
	}
	return &hc, nil
}

func (hc *Hellclient) updatePrompt() {
	var sb strings.Builder

	unread, err := hc.client.GetUnreadNotifications(context.Background(), nil, nil, 0)
	if err == nil {
		sb.WriteString(fmt.Sprintf("ur:%v ", unread.Count))
	}
	if hc.multiLineMode {
		sb.WriteString("MULTI-LINE ")
	}
	if hc.isPaused {
		sb.WriteString("STREAMING PAUSED ")
	}

	sb.WriteString("Hell> ")
	hc.rl.SetPrompt(sb.String())
}

func (hc *Hellclient) pause(on bool) {
	hc.isPaused = on
	hc.updatePrompt()
	hc.printPauseBuffer()
}

func (hc *Hellclient) togglepause() {
	hc.isPaused = !hc.isPaused
	hc.updatePrompt()
	hc.printPauseBuffer()
}

func (hc *Hellclient) printPauseBuffer() {
	if !hc.isPaused {
		for _, action := range hc.actionBuffer {
			action()
		}
		hc.actionBuffer = nil
	}
}

func (hc *Hellclient) lock() {
	hc.block.Lock()
}

func (hc *Hellclient) unlock() {
	hc.block.Unlock()
}