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}