mirror of
https://git.mills.io/saltyim/saltyim.git
synced 2024-06-28 09:41:02 +00:00
6a92c9d30a
TODO: - [ ] Remove `replace` directive after testing... Co-authored-by: James Mills <prologic@shortcircuit.net.au> Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/121
153 lines
3.3 KiB
Go
153 lines
3.3 KiB
Go
package saltyim
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/avast/retry-go"
|
|
"github.com/keys-pub/keys"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"go.yarn.social/lextwt"
|
|
)
|
|
|
|
type Service struct {
|
|
mu sync.RWMutex
|
|
|
|
me *Addr
|
|
id *Identity
|
|
|
|
cli *Client
|
|
|
|
textFns map[string]MessageTextHandlerFunc
|
|
eventFns map[string]MessageEventHandlerFunc
|
|
}
|
|
|
|
type MessageTextHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyText) error
|
|
type MessageEventHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyEvent) error
|
|
|
|
func NewService(me *Addr, id *Identity) (*Service, error) {
|
|
svc := &Service{
|
|
me: me,
|
|
id: id,
|
|
textFns: make(map[string]MessageTextHandlerFunc),
|
|
eventFns: make(map[string]MessageEventHandlerFunc),
|
|
}
|
|
svc.TextFunc("ping", func(ctx context.Context, svc *Service, key *keys.EdX25519PublicKey, msg *lextwt.SaltyText) error {
|
|
return svc.Respond(msg.User.String(), "pong")
|
|
})
|
|
|
|
return svc, nil
|
|
}
|
|
|
|
func (svc *Service) SetClient(cli *Client) {
|
|
svc.mu.Lock()
|
|
defer svc.mu.Unlock()
|
|
|
|
svc.cli = cli
|
|
}
|
|
|
|
func (svc *Service) String() string {
|
|
svc.mu.RLock()
|
|
defer svc.mu.RUnlock()
|
|
|
|
buf := &bytes.Buffer{}
|
|
fmt.Fprintln(buf, "Bot: ", svc.me)
|
|
for k := range svc.textFns {
|
|
fmt.Fprintln(buf, " - TextCmd: ", k)
|
|
}
|
|
for k := range svc.eventFns {
|
|
fmt.Fprintln(buf, " - EventCmd: ", k)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func (svc *Service) Respond(user, msg string) error {
|
|
if svc.cli == nil {
|
|
return fmt.Errorf("service not connected")
|
|
}
|
|
return svc.cli.Send(user, msg)
|
|
}
|
|
|
|
func (svc *Service) Run(ctx context.Context) error {
|
|
// create the service user's client in a loop until successful
|
|
// TODO: Should this timeout? Use a context?
|
|
if err := retry.Do(func() error {
|
|
cli, err := NewClient(svc.me, WithClientIdentity(WithIdentity(svc.id)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cli.me.Refresh(); err != nil {
|
|
return err
|
|
}
|
|
svc.SetClient(cli)
|
|
return nil
|
|
},
|
|
retry.LastErrorOnly(true),
|
|
retry.OnRetry(func(n uint, err error) {
|
|
log.Debugf("retrying service user setup (try #%d): %s", n, err)
|
|
}),
|
|
); err != nil {
|
|
return fmt.Errorf("error setting up service user %s: %w", svc.me, err)
|
|
}
|
|
|
|
log.Debugf("listening for service requests as %s", svc.me)
|
|
|
|
msgch := svc.cli.Subscribe(ctx, "", "", "")
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case msg := <-msgch:
|
|
if err := svc.handle(ctx, msg); err != nil {
|
|
log.WithError(err).Println("failed to handle message")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (svc *Service) handle(ctx context.Context, msg Message) error {
|
|
decoded, err := lextwt.ParseSalty(msg.Text)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch m := decoded.(type) {
|
|
case *lextwt.SaltyText:
|
|
fields := strings.Fields(m.LiteralText())
|
|
svc.mu.RLock()
|
|
defer svc.mu.RUnlock()
|
|
|
|
if fn, ok := svc.textFns[strings.ToUpper(fields[0])]; ok {
|
|
err = fn(ctx, svc, msg.Key, m)
|
|
}
|
|
case *lextwt.SaltyEvent:
|
|
svc.mu.RLock()
|
|
defer svc.mu.RUnlock()
|
|
|
|
if fn, ok := svc.eventFns[strings.ToUpper(m.Command)]; ok {
|
|
err = fn(ctx, svc, msg.Key, m)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (svc *Service) TextFunc(name string, fn MessageTextHandlerFunc) {
|
|
svc.mu.Lock()
|
|
defer svc.mu.Unlock()
|
|
|
|
svc.textFns[strings.ToUpper(name)] = fn
|
|
}
|
|
|
|
func (svc *Service) EventFunc(name string, fn MessageEventHandlerFunc) {
|
|
svc.mu.Lock()
|
|
defer svc.mu.Unlock()
|
|
|
|
svc.eventFns[strings.ToUpper(name)] = fn
|
|
}
|