plugins/tell.go (view raw)
1package plugins
2
3import (
4 "bytes"
5 "crypto/rand"
6 "encoding/gob"
7 "fmt"
8 "io"
9 "sort"
10 "strings"
11 "time"
12
13 "git.icyphox.sh/paprika/database"
14 "github.com/dgraph-io/badger/v3"
15 "github.com/dustin/go-humanize"
16 "gopkg.in/irc.v3"
17)
18
19func init() {
20 Register(Tell{})
21}
22
23type Tell struct {
24 From string
25 To string
26 Message string
27 Time time.Time
28}
29
30func (Tell) Triggers() []string {
31 return []string{".tell", ""}
32}
33
34// Encodes message into encoding/gob for storage.
35func (t *Tell) saveTell() error {
36 data := bytes.Buffer{}
37 enc := gob.NewEncoder(&data)
38
39 if err := enc.Encode(t); err != nil {
40 return err
41 }
42 // Store key as 'tell/nick/randbytes'; should help with
43 // easy prefix scans for tells.
44 rnd := make([]byte, 8)
45 rand.Read(rnd)
46
47 key := []byte(fmt.Sprintf("tell/%s/", t.To))
48 key = append(key, rnd...)
49 err := database.DB.Set(key, data.Bytes())
50 if err != nil {
51 return err
52 }
53 return nil
54}
55
56// Decodes tell data from encoding/gob into a Tell.
57func getTell(data io.Reader) (*Tell, error) {
58 dec := gob.NewDecoder(data)
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/" + 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 var r bytes.Reader
104 tell, err := getTell(&r)
105 if err != nil {
106 return err
107 }
108 tells = append(tells, *tell)
109 return nil
110 })
111 if err != nil {
112 return err
113 }
114 err = txn.Delete(k)
115 if err != nil {
116 return err
117 }
118 }
119 return nil
120 })
121 if err != nil {
122 return "", err
123 }
124
125 // Sort tells by time.
126 sort.Slice(tells, func(i, j int) bool {
127 return tells[i].Time.Before(tells[j].Time)
128 })
129
130 // Formatted tells in a slice, for joining into a string
131 // later.
132 tellsFmtd := []string{}
133 for _, tell := range tells {
134 tellsFmtd = append(
135 tellsFmtd,
136 fmt.Sprintf(
137 "%s sent you a message %s: %s",
138 tell.From, humanize.Time(tell.Time), tell.Message,
139 ),
140 )
141 }
142
143 return strings.Join(tellsFmtd, "\n"), &IsPrivateNotice{To: t.To}
144 }
145}