mcchunkie/plugins/plugins.go
Bryce Chidester 6bf07b2598 Include a "body" of the fictitious filename with images
Some clients (like Cinny.in) expect there to be a content.body element present
in m.image messages, and will complain "Malformed event" if this value is empty.
2022-08-12 11:14:49 -07:00

312 lines
6.9 KiB
Go

package plugins
import (
"bytes"
"encoding/json"
"image"
"image/png"
"io"
"net/http"
"regexp"
"strings"
"time"
"github.com/gomarkdown/markdown"
"github.com/matrix-org/gomatrix"
)
// PluginStore matches MCStore. This allows the main store to be used by
// plugins.
type PluginStore interface {
Set(key, values string)
Get(key string) (string, error)
}
// Plugin defines the interface a plugin must implement to be used by
// mcchunkie.
type Plugin interface {
// Descr returns a brief description of the plugin.
Descr() string
// Match determines if the plugin's main Respond function should be
// called
Match(user, message string) bool
// Name should return the human readable name of the bot
Name() string
// Re returns the regular expression that a plugin uses to "match"
Re() string
// RespondMatrix responds to a Matrix "m.text" event
RespondText(c *gomatrix.Client, ev *gomatrix.Event, user, path string) error
// Process is the processed response from the plugin. This is useful for
// running the plugins outside of the context of Matrix.
Process(from, message string) string
// SetStore exposes the top level MCStore to a plugin
SetStore(s PluginStore)
}
// NameRE matches the "friendly" name. This is typically used in tab
// completion.
var NameRE = regexp.MustCompile(`@(.+):.+$`)
// ToMe returns true of the message pertains to the bot
func ToMe(user, message string) bool {
u := NameRE.ReplaceAllString(user, "$1")
return strings.Contains(message, u)
}
// RemoveName removes the friendly name from a given message
func RemoveName(user, message string) string {
n := NameRE.ReplaceAllString(user, "$1")
return strings.ReplaceAll(message, n+": ", "")
}
// HTTPRequest has the bits for making http requests
type HTTPRequest struct {
Client http.Client
Request *http.Request
Timeout time.Duration
URL string
Method string
ReqBody interface{}
ResBody interface{}
}
// DoJSON is a general purpose http mechanic that can be used to get, post..
// what evs. The response is always expected to be json
func (h *HTTPRequest) DoJSON() (err error) {
h.Client.Timeout = h.Timeout
if h.Method == "" {
h.Method = "GET"
}
if h.ReqBody != nil {
// We have a request to send to the server
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(h.ReqBody)
if err != nil {
return err
}
h.Request, err = http.NewRequest(h.Method, h.URL, buf)
} else {
// Just gimme dem datas
h.Request, err = http.NewRequest(h.Method, h.URL, nil)
}
if err != nil {
return err
}
h.Request.Header.Set("Content-Type", "application/json")
res, err := h.Client.Do(h.Request)
if res != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
if h.ResBody != nil && res.Body != nil {
return json.NewDecoder(res.Body).Decode(&h.ResBody)
}
return nil
}
// SendText sends a text message to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendText(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
_, err = c.SendText(roomID, message)
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendEmote sends an emote to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendEmote(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
_, err = c.SendMessageEvent(roomID, "m.room.message", gomatrix.GetHTMLMessage("m.emote", message))
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendHTML sends an html message to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendHTML(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
_, err = c.SendMessageEvent(roomID, "m.room.message", gomatrix.GetHTMLMessage("m.text", message))
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendMDNotice sends an html notice to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendMDNotice(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
md := []byte(message)
html := markdown.ToHTML(md, nil, nil)
_, err = c.SendMessageEvent(roomID, "m.room.message", gomatrix.GetHTMLMessage("m.notice", string(html)))
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendUnescNotice sends an text notice to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendUnescNotice(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
// Undo the escaping
_, err = c.SendMessageEvent(roomID, "m.room.message", gomatrix.HTMLMessage{
Body: message,
MsgType: "m.notice",
Format: "org.matrix.custom.text",
FormattedBody: message,
})
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendNotice sends an text notice to a given room. It pretends to be
// "typing" by calling UserTyping for the caller.
func SendNotice(c *gomatrix.Client, roomID, message string) error {
_, err := c.UserTyping(roomID, true, 3)
if err != nil {
return err
}
_, err = c.SendMessageEvent(roomID, "m.room.message", gomatrix.GetHTMLMessage("m.notice", message))
if err != nil {
return err
}
_, err = c.UserTyping(roomID, false, 0)
if err != nil {
return err
}
return nil
}
// SendMD takes markdown and converts it to an html message.
func SendMD(c *gomatrix.Client, roomID, message string) error {
md := []byte(message)
html := markdown.ToHTML(md, nil, nil)
return SendHTML(c, roomID, string(html))
}
// SendImage takes an image and sends it!.
func SendImage(c *gomatrix.Client, roomID string, img *image.RGBA) error {
r, w := io.Pipe()
go func() {
defer w.Close()
_ = png.Encode(w, img)
}()
mediaURL, err := c.UploadToContentRepo(r, "image/png", 0)
if err != nil {
return err
}
_, err = c.SendImage(roomID, "embedded_image.png", mediaURL.ContentURI)
if err != nil {
return err
}
return nil
}
// Plugins is a collection of our plugins. An instance of this is iterated
// over for each message the bot receives.
type Plugins []Plugin
// Plugs defines the "enabled" plugins.
var Plugs = Plugins{
&Ban{},
&BananaStab{},
&Beat{},
&Beer{},
&BotSnack{},
&Covid{},
&DMR{},
&Feder{},
&Groan{},
&Ham{},
&HighFive{},
&Hi{},
&LoveYou{},
&OpenBSDMan{},
&PGP{},
&Palette{},
&RFC{},
&Salute{},
&Snap{},
&Source{},
&Thanks{},
&Homestead{},
&Toki{},
&Version{},
&Wb{},
&Weather{},
}