all repos — paprika @ 56b0b38f6ae1a7d53905f9f0cf3cf7c9aaf6055e

go rewrite of taigabot

plugins/tell.go (view raw)

  1package plugins
  2
  3import (
  4	"bytes"
  5	"crypto/rand"
  6	"encoding/gob"
  7	"fmt"
  8	"sort"
  9	"strings"
 10	"time"
 11
 12	"git.icyphox.sh/paprika/database"
 13	"github.com/dgraph-io/badger/v3"
 14	"github.com/dustin/go-humanize"
 15	"gopkg.in/irc.v3"
 16)
 17
 18func init() {
 19	Register(Tell{})
 20}
 21
 22type Tell struct {
 23	From    string
 24	To      string
 25	Message string
 26	Time    time.Time
 27}
 28
 29func (Tell) Triggers() []string {
 30	return []string{".tell", ""}
 31}
 32
 33// Encodes message into encoding/gob for storage.
 34func (t *Tell) saveTell() error {
 35	data := bytes.Buffer{}
 36	enc := gob.NewEncoder(&data)
 37
 38	if err := enc.Encode(t); err != nil {
 39		return err
 40	}
 41	// Store key as 'tell/nick/randbytes'; should help with
 42	// easy prefix scans for tells.
 43	rnd := make([]byte, 8)
 44	rand.Read(rnd)
 45
 46	key := []byte(fmt.Sprintf("tell/%s/", t.To))
 47	key = append(key, rnd...)
 48	err := database.DB.Set(key, data.Bytes())
 49	if err != nil {
 50		return err
 51	}
 52	return nil
 53}
 54
 55// Decodes tell data from encoding/gob into a Tell.
 56func getTell(data []byte) (*Tell, error) {
 57	r := bytes.NewReader(data)
 58	dec := gob.NewDecoder(r)
 59	t := Tell{}
 60	if err := dec.Decode(&t); err != nil {
 61		return nil, err
 62	}
 63
 64	return &t, nil
 65}
 66
 67func (t Tell) Execute(m *irc.Message) (string, error) {
 68	parts := strings.SplitN(m.Trailing(), " ", 3)
 69
 70	if parts[0] == ".tell" {
 71		// No message passed.
 72		if len(parts) == 2 {
 73			return "Usage: .tell <nick> <message>", nil
 74		}
 75
 76		t.From = strings.ToLower(m.Prefix.Name)
 77		t.To = strings.ToLower(parts[1])
 78		t.Message = parts[2]
 79		t.Time = time.Now()
 80
 81		if err := t.saveTell(); err != nil {
 82			return "Error saving message", err
 83		}
 84
 85		return "Your message will be sent!", &IsPrivateNotice{t.From}
 86	} else {
 87		// React to all other messages here.
 88		// Iterate over key prefixes to check if our tell
 89		// recepient has shown up. Then send his tell and delete
 90		// the keys.
 91
 92		// All pending tells.
 93		tells := []Tell{}
 94
 95		err := database.DB.Update(func(txn *badger.Txn) error {
 96			it := txn.NewIterator(badger.DefaultIteratorOptions)
 97			defer it.Close()
 98			prefix := []byte("tell/" + strings.ToLower(m.Prefix.Name))
 99			for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
100				item := it.Item()
101				k := item.Key()
102				err := item.Value(func(v []byte) error {
103					tell, err := getTell(v)
104					if err != nil {
105						return fmt.Errorf("degobbing: %w", err)
106					}
107					tells = append(tells, *tell)
108					return nil
109				})
110				if err != nil {
111					return fmt.Errorf("iterating: %w", err)
112				}
113				err = txn.Delete(k)
114				if err != nil {
115					return fmt.Errorf("deleting key: %w", err)
116				}
117			}
118			return nil
119		})
120		if err != nil {
121			return "", fmt.Errorf("fetching tells: %w", err)
122		}
123
124		// No tells for this user.
125		if len(tells) == 0 {
126			return "", NoReply
127		}
128
129		// Sort tells by time.
130		sort.Slice(tells, func(i, j int) bool {
131			return tells[i].Time.Before(tells[j].Time)
132		})
133
134		// Formatted tells in a slice, for joining into a string
135		// later.
136		tellsFmtd := strings.Builder{}
137		for _, tell := range tells {
138			s := fmt.Sprintf(
139				"%s sent you a message %s: %s\n",
140				tell.From, humanize.Time(tell.Time), tell.Message,
141			)
142			tellsFmtd.WriteString(s)
143		}
144		return tellsFmtd.String(), &IsPrivateNotice{To: tells[0].To}
145	}
146}