/
actor.go
130 lines (105 loc) · 3.4 KB
/
actor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Package filesay contains an actor to paste a remote URL as chat
// commands i.e. for bulk banning users
package filesay
import (
"bufio"
"context"
"fmt"
"net/http"
"net/url"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
const (
actorName = "filesay"
httpTimeout = 5 * time.Second
)
var (
formatMessage plugins.MsgFormatter
send plugins.SendMessageFunc
)
// Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) error {
formatMessage = args.FormatMessage
send = args.SendMessage
args.RegisterActor(actorName, func() plugins.Actor { return &actor{} })
args.RegisterActorDocumentation(plugins.ActionDocumentation{
Description: "Takes the content of an URL and pastes it to the current channel",
Name: "FileSay",
Type: actorName,
Fields: []plugins.ActionDocumentationField{
{
Default: "",
Description: "Source of the content to post",
Key: "source",
Name: "source",
Optional: false,
SupportTemplate: true,
Type: plugins.ActionDocumentationFieldTypeString,
},
},
})
return nil
}
type actor struct{}
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
ptrStringEmpty := func(v string) *string { return &v }("")
source, err := formatMessage(attrs.MustString("source", ptrStringEmpty), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "executing source template")
}
if source == "" {
return false, errors.New("source template evaluated to empty string")
}
if _, err := url.Parse(source); err != nil {
return false, errors.Wrap(err, "parsing URL")
}
ctx, cancel := context.WithTimeout(context.Background(), httpTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, source, nil)
if err != nil {
return false, errors.Wrap(err, "creating HTTP request")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, errors.Wrap(err, "executing HTTP request")
}
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.WithError(err).Error("closing response body (leaked fd)")
}
}()
if resp.StatusCode != http.StatusOK {
return false, errors.Errorf("http status %d", resp.StatusCode)
}
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
if err = send(&irc.Message{
Command: "PRIVMSG",
Params: []string{
plugins.DeriveChannel(m, eventData),
scanner.Text(),
},
}); err != nil {
return false, errors.Wrap(err, "sending message")
}
}
return false, nil
}
func (actor) IsAsync() bool { return true }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "source", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.MustHaveNoUnknowFields,
helpers.SchemaValidateTemplateField(tplValidator, "source"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil
}