all repos — paprika @ 014e7ec5f807bba7c2d6ccb9b6fcb699b0f7e185

go rewrite of taigabot

Initial implementation of tells

But it no worky.
Anirudh Oppiliappan x@icyphox.sh
Tue, 28 Dec 2021 17:27:28 +0530
commit

014e7ec5f807bba7c2d6ccb9b6fcb699b0f7e185

parent

e793cf577d4223e920af2930fa2aab09f0b874f7

4 files changed, 171 insertions(+), 5 deletions(-)

jump to
M main.gomain.go

@@ -29,6 +29,13 @@ cmd = "NOTICE"

} msg := irc.Message{Command: cmd} target := m.Params[0] + + if serr, ok := err.(*plugins.IsPrivateNotice); ok { + target = serr.To + cmd = "NOTICE" + err = nil + } + split := strings.Split(response, "\n") if errors.Is(err, plugins.IsRaw) {
M plugins/plugins.goplugins/plugins.go

@@ -2,6 +2,7 @@ package plugins

import ( "errors" + "fmt" "strings" "gopkg.in/irc.v3"

@@ -26,12 +27,24 @@ // This is useful for broad prefix matching or future regexp

// functions, other special errors may need defining // to determin priority or to "concatenate" output. var NoReply = errors.New("No Reply") + // This error indicates we are sending a NOTICE instead of a PRIVMSG var IsNotice = errors.New("Is Notice") + // This means the string(s) we are returning are raw IRC commands // that need to be written verbatim. var IsRaw = errors.New("Is Raw") +// This error indicates that the message is a NOTICE, along with a +// recepient. +type IsPrivateNotice struct { + To string +} + +func (e *IsPrivateNotice) Error() string { + return fmt.Sprintf("Is Private Notice: %s", e.To) +} + // Due to racey nature of the handler in main.go being invoked as a goroutine, // it's hard to have race free way of building correct state of the IRC world. // This is a temporary (lol) hack to check if the PRIVMSG target looks like a

@@ -62,8 +75,8 @@ // ALSO NOTE: RFC1459 defines a "nick" as

// <nick> ::= <letter> { <letter> | <number> | <special> } // But I have seen some networks that allow special/number as the first letter. return sym > 32 /* SPACE */ && sym < 48 /* 0 */ || - sym > 58 /* : */ && sym < 65 /* A */ || - sym == 126 /* ~ */ + sym > 58 /* : */ && sym < 65 /* A */ || + sym == 126 /* ~ */ } // Checks for triggers in a message and executes its
A plugins/tell.go

@@ -0,0 +1,145 @@

+package plugins + +import ( + "bytes" + "crypto/rand" + "encoding/gob" + "fmt" + "io" + "sort" + "strings" + "time" + + "git.icyphox.sh/paprika/database" + "github.com/dgraph-io/badger/v3" + "github.com/dustin/go-humanize" + "gopkg.in/irc.v3" +) + +func init() { + Register(Tell{}) +} + +type Tell struct { + From string + To string + Message string + Time time.Time +} + +func (Tell) Triggers() []string { + return []string{".tell", ""} +} + +// Encodes message into encoding/gob for storage. +func (t *Tell) saveTell() error { + data := bytes.Buffer{} + enc := gob.NewEncoder(&data) + + if err := enc.Encode(t); err != nil { + return err + } + // Store key as 'tell/nick/randbytes'; should help with + // easy prefix scans for tells. + rnd := make([]byte, 8) + rand.Read(rnd) + + key := []byte(fmt.Sprintf("tell/%s/", t.To)) + key = append(key, rnd...) + err := database.DB.Set(key, data.Bytes()) + if err != nil { + return err + } + return nil +} + +// Decodes tell data from encoding/gob into a Tell. +func getTell(data io.Reader) (*Tell, error) { + dec := gob.NewDecoder(data) + t := Tell{} + if err := dec.Decode(&t); err != nil { + return nil, err + } + + return &t, nil +} + +func (t Tell) Execute(m *irc.Message) (string, error) { + parts := strings.SplitN(m.Trailing(), " ", 3) + + if parts[0] == ".tell" { + // No message passed. + if len(parts) == 2 { + return "Usage: .tell <nick> <message>", nil + } + + t.From = strings.ToLower(m.Prefix.Name) + t.To = strings.ToLower(parts[1]) + t.Message = parts[2] + t.Time = time.Now() + + if err := t.saveTell(); err != nil { + return "Error saving message", err + } + + return "Your message will be sent!", &IsPrivateNotice{t.From} + } else { + // React to all other messages here. + // Iterate over key prefixes to check if our tell + // recepient has shown up. Then send his tell and delete + // the keys. + + // All pending tells. + tells := []Tell{} + + err := database.DB.Update(func(txn *badger.Txn) error { + it := txn.NewIterator(badger.DefaultIteratorOptions) + defer it.Close() + prefix := []byte("tell/" + m.Prefix.Name) + for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { + item := it.Item() + k := item.Key() + err := item.Value(func(v []byte) error { + var r bytes.Reader + tell, err := getTell(&r) + if err != nil { + return err + } + tells = append(tells, *tell) + return nil + }) + if err != nil { + return err + } + err = txn.Delete(k) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return "", err + } + + // Sort tells by time. + sort.Slice(tells, func(i, j int) bool { + return tells[i].Time.Before(tells[j].Time) + }) + + // Formatted tells in a slice, for joining into a string + // later. + tellsFmtd := []string{} + for _, tell := range tells { + tellsFmtd = append( + tellsFmtd, + fmt.Sprintf( + "%s sent you a message %s: %s", + tell.From, humanize.Time(tell.Time), tell.Message, + ), + ) + } + + return strings.Join(tellsFmtd, "\n"), &IsPrivateNotice{To: t.To} + } +}