mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-08 08:10:08 +00:00
Add code linting / binary publishing (#3)
This commit is contained in:
parent
8b575f7771
commit
0ae34112b6
11 changed files with 426 additions and 100 deletions
59
.golangci.yml
Normal file
59
.golangci.yml
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Derived from https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||
|
||||
---
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- config
|
||||
skip-files:
|
||||
- assets.go
|
||||
- bindata.go
|
||||
|
||||
output:
|
||||
format: tab
|
||||
|
||||
linters-settings:
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 60
|
||||
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 15
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||
- deadcode # Finds unused code [fast: true, auto-fix: false]
|
||||
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
- exportloopref # checks for pointers to enclosing loop variables [fast: true, auto-fix: false]
|
||||
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
|
||||
- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
|
||||
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
||||
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
||||
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
- noctx # noctx finds sending http request without context.Context [fast: true, auto-fix: false]
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||
- structcheck # Finds unused struct fields [fast: true, auto-fix: false]
|
||||
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
||||
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
|
||||
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
- varcheck # Finds unused global variables and constants [fast: true, auto-fix: false]
|
||||
|
||||
...
|
13
.repo-runner.yaml
Normal file
13
.repo-runner.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
|
||||
image: "reporunner/golang-alpine"
|
||||
checkout_dir: /go/src/github.com/Luzifer/twitch-bot
|
||||
|
||||
commands:
|
||||
- make lint test publish
|
||||
|
||||
environment:
|
||||
DRAFT: "false"
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: on
|
||||
MOD_MODE: readonly
|
14
Makefile
14
Makefile
|
@ -1,4 +1,16 @@
|
|||
default:
|
||||
default: lint test
|
||||
|
||||
lint:
|
||||
golangci-lint run --timeout=5m
|
||||
|
||||
publish:
|
||||
curl -sSLo golang.sh https://raw.githubusercontent.com/Luzifer/github-publish/master/golang.sh
|
||||
bash golang.sh
|
||||
|
||||
test:
|
||||
go test -cover -v .
|
||||
|
||||
# --- Wiki Updates
|
||||
|
||||
pull_wiki:
|
||||
git subtree pull --prefix=wiki https://github.com/Luzifer/twitch-bot.wiki.git master --squash
|
||||
|
|
|
@ -45,7 +45,7 @@ func init() {
|
|||
return errors.Wrap(err, "encoding script input")
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
cmd := exec.CommandContext(ctx, command[0], command[1:]...) // #nosec G204 // This is expected to call a command with parameters
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = stdin
|
||||
|
|
5
irc.go
5
irc.go
|
@ -19,7 +19,6 @@ const (
|
|||
badgeFounder = "founder"
|
||||
badgeModerator = "moderator"
|
||||
badgeSubscriber = "subscriber"
|
||||
badgeVIP = "vip"
|
||||
)
|
||||
|
||||
type ircHandler struct {
|
||||
|
@ -135,7 +134,7 @@ func (i ircHandler) handlePermit(m *irc.Message) {
|
|||
}
|
||||
|
||||
msgParts := strings.Split(m.Trailing(), " ")
|
||||
if len(msgParts) != 2 {
|
||||
if len(msgParts) != 2 { //nolint:gomnd // This is not a magic number but just an expected count
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -230,7 +229,7 @@ func (ircHandler) ParseBadgeLevels(m *irc.Message) badgeCollection {
|
|||
badges := strings.Split(badgeString, ",")
|
||||
for _, b := range badges {
|
||||
badgeParts := strings.Split(b, "/")
|
||||
if len(badgeParts) != 2 {
|
||||
if len(badgeParts) != 2 { //nolint:gomnd // This is not a magic number but just an expected count
|
||||
log.WithField("badge", b).Warn("Malformed badge found")
|
||||
continue
|
||||
}
|
||||
|
|
13
main.go
13
main.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -12,6 +13,8 @@ import (
|
|||
"github.com/Luzifer/rconfig/v2"
|
||||
)
|
||||
|
||||
const ircReconnectDelay = 100 * time.Millisecond
|
||||
|
||||
var (
|
||||
cfg = struct {
|
||||
CommandTimeout time.Duration `flag:"command-timeout" default:"30s" description:"Timeout for command execution"`
|
||||
|
@ -32,6 +35,13 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
for _, a := range os.Args {
|
||||
if strings.HasPrefix(a, "-test.") {
|
||||
// Skip initialize for test run
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rconfig.AutoEnv(true)
|
||||
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||
log.Fatalf("Unable to parse commandline options: %s", err)
|
||||
|
@ -49,6 +59,7 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
//nolint: gocognit,gocyclo // Complexity is a little too high but makes no sense to split
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
|
@ -93,7 +104,7 @@ func main() {
|
|||
if err := irc.Run(); err != nil {
|
||||
log.WithError(err).Error("IRC run exited unexpectedly")
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(ircReconnectDelay)
|
||||
ircDisconnected <- struct{}{}
|
||||
}()
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ func formatMessage(tplString string, m *irc.Message, r *rule, fields map[string]
|
|||
}
|
||||
|
||||
fields["msg"] = m
|
||||
fields["permitTimeout"] = int64(*&config.PermitTimeout / time.Second)
|
||||
fields["permitTimeout"] = int64(config.PermitTimeout / time.Second)
|
||||
fields["username"] = m.User
|
||||
|
||||
if m.Command == "PRIVMSG" && len(m.Params) > 0 {
|
||||
|
|
246
rule.go
246
rule.go
|
@ -51,8 +51,6 @@ func (r rule) MatcherID() string {
|
|||
}
|
||||
|
||||
func (r *rule) Matches(m *irc.Message, event *string) bool {
|
||||
var err error
|
||||
|
||||
var (
|
||||
badges = ircHandler{}.ParseBadgeLevels(m)
|
||||
logger = log.WithFields(log.Fields{
|
||||
|
@ -61,69 +59,28 @@ func (r *rule) Matches(m *irc.Message, event *string) bool {
|
|||
})
|
||||
)
|
||||
|
||||
// Check Channel match
|
||||
if len(r.MatchChannels) > 0 {
|
||||
if len(m.Params) == 0 || !str.StringInSlice(m.Params[0], r.MatchChannels) {
|
||||
logger.Trace("Non-Match: Channel")
|
||||
for _, matcher := range []func(*log.Entry, *irc.Message, *string, badgeCollection) bool{
|
||||
r.allowExecuteChannelWhitelist,
|
||||
r.allowExecuteUserWhitelist,
|
||||
r.allowExecuteEventWhitelist,
|
||||
r.allowExecuteMessageMatcherWhitelist,
|
||||
r.allowExecuteMessageMatcherBlacklist,
|
||||
r.allowExecuteBadgeBlacklist,
|
||||
r.allowExecuteBadgeWhitelist,
|
||||
r.allowExecuteDisableOnPermit,
|
||||
r.allowExecuteCooldown,
|
||||
r.allowExecuteDisableOnOffline,
|
||||
} {
|
||||
if !matcher(logger, m, event, badges) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.MatchUsers) > 0 {
|
||||
if !str.StringInSlice(strings.ToLower(m.User), r.MatchUsers) {
|
||||
logger.Trace("Non-Match: Users")
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Nothing objected: Matches!
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Event match
|
||||
if r.MatchEvent != nil {
|
||||
if event == nil || *r.MatchEvent != *event {
|
||||
logger.Trace("Non-Match: Event")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check Message match
|
||||
if r.MatchMessage != nil {
|
||||
// If the regexp was not yet compiled, cache it
|
||||
if r.matchMessage == nil {
|
||||
if r.matchMessage, err = regexp.Compile(*r.MatchMessage); err != nil {
|
||||
logger.WithError(err).Error("Unable to compile expression")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the message matches
|
||||
if !r.matchMessage.MatchString(m.Trailing()) {
|
||||
logger.Trace("Non-Match: Message")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.DisableOnMatchMessages) > 0 {
|
||||
// If the regexps were not pre-compiled, do it now
|
||||
if len(r.disableOnMatchMessages) != len(r.DisableOnMatchMessages) {
|
||||
r.disableOnMatchMessages = nil
|
||||
for _, dm := range r.DisableOnMatchMessages {
|
||||
dmr, err := regexp.Compile(dm)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unable to compile expression")
|
||||
return false
|
||||
}
|
||||
r.disableOnMatchMessages = append(r.disableOnMatchMessages, dmr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rex := range r.disableOnMatchMessages {
|
||||
if rex.MatchString(m.Trailing()) {
|
||||
logger.Trace("Non-Match: Disable-On-Message")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether user has one of the disable rules
|
||||
func (r *rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
for _, b := range r.DisableOn {
|
||||
if badges.Has(b) {
|
||||
logger.Tracef("Non-Match: Disable-Badge %s", b)
|
||||
|
@ -131,53 +88,164 @@ func (r *rule) Matches(m *irc.Message, event *string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// Check whether user has at least one of the enable rules
|
||||
if len(r.EnableOn) > 0 {
|
||||
var userHasEnableBadge bool
|
||||
for _, b := range r.EnableOn {
|
||||
if badges.Has(b) {
|
||||
userHasEnableBadge = true
|
||||
}
|
||||
}
|
||||
if !userHasEnableBadge {
|
||||
logger.Trace("Non-Match: No enable-badges")
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteBadgeWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.EnableOn) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.EnableOn {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check on permit
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.MatchChannels) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if len(m.Params) == 0 || (!str.StringInSlice(m.Params[0], r.MatchChannels) && !str.StringInSlice(strings.TrimPrefix(m.Params[0], "#"), r.MatchChannels)) {
|
||||
logger.Trace("Non-Match: Channel")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteCooldown(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.Cooldown == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !timerStore.InCooldown(r.MatcherID(), *r.Cooldown) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteDisableOnOffline(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if !r.DisableOnOffline {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
streamLive, err := twitch.HasLiveStream(strings.TrimLeft(m.Params[0], "#"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unable to determine live status")
|
||||
return false
|
||||
}
|
||||
if !streamLive {
|
||||
logger.Trace("Non-Match: Stream offline")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteDisableOnPermit(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.DisableOnPermit && timerStore.HasPermit(m.Params[0], m.User) {
|
||||
logger.Trace("Non-Match: Permit")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether rule is in cooldown
|
||||
if r.Cooldown != nil && timerStore.InCooldown(r.MatcherID(), *r.Cooldown) {
|
||||
var userHasSkipBadge bool
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
userHasSkipBadge = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteEventWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.MatchEvent == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if event == nil || *r.MatchEvent != *event {
|
||||
logger.Trace("Non-Match: Event")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteMessageMatcherBlacklist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.DisableOnMatchMessages) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
// If the regexps were not pre-compiled, do it now
|
||||
if len(r.disableOnMatchMessages) != len(r.DisableOnMatchMessages) {
|
||||
r.disableOnMatchMessages = nil
|
||||
for _, dm := range r.DisableOnMatchMessages {
|
||||
dmr, err := regexp.Compile(dm)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unable to compile expression")
|
||||
return false
|
||||
}
|
||||
r.disableOnMatchMessages = append(r.disableOnMatchMessages, dmr)
|
||||
}
|
||||
if !userHasSkipBadge {
|
||||
logger.Trace("Non-Match: On cooldown")
|
||||
}
|
||||
|
||||
for _, rex := range r.disableOnMatchMessages {
|
||||
if rex.MatchString(m.Trailing()) {
|
||||
logger.Trace("Non-Match: Disable-On-Message")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if r.DisableOnOffline {
|
||||
streamLive, err := twitch.HasLiveStream(strings.TrimLeft(m.Params[0], "#"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unable to determine live status")
|
||||
return false
|
||||
}
|
||||
if !streamLive {
|
||||
logger.Trace("Non-Match: Stream offline")
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteMessageMatcherWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.MatchMessage == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// If the regexp was not yet compiled, cache it
|
||||
if r.matchMessage == nil {
|
||||
if r.matchMessage, err = regexp.Compile(*r.MatchMessage); err != nil {
|
||||
logger.WithError(err).Error("Unable to compile expression")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing objected: Matches!
|
||||
// Check whether the message matches
|
||||
if !r.matchMessage.MatchString(m.Trailing()) {
|
||||
logger.Trace("Non-Match: Message")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteUserWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.MatchUsers) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !str.StringInSlice(strings.ToLower(m.User), r.MatchUsers) {
|
||||
logger.Trace("Non-Match: Users")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
164
rule_test.go
Normal file
164
rule_test.go
Normal file
|
@ -0,0 +1,164 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
testLogger = logrus.NewEntry(logrus.StandardLogger())
|
||||
testBadgeLevel0 = func(i int) *int { return &i }(0)
|
||||
)
|
||||
|
||||
func TestAllowExecuteBadgeBlacklist(t *testing.T) {
|
||||
r := &rule{DisableOn: []string{badgeBroadcaster}}
|
||||
|
||||
if r.allowExecuteBadgeBlacklist(testLogger, nil, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Execution allowed on blacklisted badge")
|
||||
}
|
||||
|
||||
if !r.allowExecuteBadgeBlacklist(testLogger, nil, nil, badgeCollection{badgeModerator: testBadgeLevel0}) {
|
||||
t.Error("Execution denied without blacklisted badge")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteBadgeWhitelist(t *testing.T) {
|
||||
r := &rule{EnableOn: []string{badgeBroadcaster}}
|
||||
|
||||
if r.allowExecuteBadgeWhitelist(testLogger, nil, nil, badgeCollection{badgeModerator: testBadgeLevel0}) {
|
||||
t.Error("Execution allowed without whitelisted badge")
|
||||
}
|
||||
|
||||
if !r.allowExecuteBadgeWhitelist(testLogger, nil, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Execution denied with whitelisted badge")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteChannelWhitelist(t *testing.T) {
|
||||
r := &rule{MatchChannels: []string{"#mychannel", "otherchannel"}}
|
||||
|
||||
for m, exp := range map[string]bool{
|
||||
":amy!amy@foo.example.com PRIVMSG #mychannel :Testing": true,
|
||||
":amy!amy@foo.example.com PRIVMSG #otherchannel :Testing": true,
|
||||
":amy!amy@foo.example.com PRIVMSG #randomchannel :Testing": false,
|
||||
":amy!amy@foo.example.com JOIN #mychannel": true,
|
||||
":tmi.twitch.tv CLEARCHAT #mychannel": true,
|
||||
":tmi.twitch.tv CLEARCHAT #mychannel :ronni": true,
|
||||
":tmi.twitch.tv CLEARCHAT #dallas": false,
|
||||
"@msg-id=slow_off :tmi.twitch.tv NOTICE #mychannel :This room is no longer in slow mode.": true,
|
||||
} {
|
||||
if res := r.allowExecuteChannelWhitelist(testLogger, irc.MustParseMessage(m), nil, badgeCollection{}); res != exp {
|
||||
t.Errorf("Message %q yield unxpected result: exp=%v res=%v", m, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteCooldown(t *testing.T) {
|
||||
r := &rule{Cooldown: func(i time.Duration) *time.Duration { return &i }(time.Minute), SkipCooldownFor: []string{badgeBroadcaster}}
|
||||
|
||||
if !r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Initial call was not allowed")
|
||||
}
|
||||
|
||||
// Add cooldown
|
||||
timerStore.AddCooldown(r.MatcherID())
|
||||
|
||||
if r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Call after cooldown added was allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with skip badge was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteDisableOnOffline(t *testing.T) {
|
||||
r := &rule{DisableOnOffline: true}
|
||||
|
||||
// Fake cache entries to prevent calling the real Twitch API
|
||||
twitch.apiCache.Set([]string{"hasLiveStream", "channel1"}, time.Minute, true)
|
||||
twitch.apiCache.Set([]string{"hasLiveStream", "channel2"}, time.Minute, false)
|
||||
|
||||
for ch, exp := range map[string]bool{
|
||||
"channel1": true,
|
||||
"channel2": false,
|
||||
} {
|
||||
if res := r.allowExecuteDisableOnOffline(testLogger, irc.MustParseMessage(fmt.Sprintf("PRIVMSG #%s :test", ch)), nil, badgeCollection{}); res != exp {
|
||||
t.Errorf("Channel %q yield an unexpected result: exp=%v res=%v", ch, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteDisableOnPermit(t *testing.T) {
|
||||
r := &rule{DisableOnPermit: true}
|
||||
|
||||
// Permit is using global configuration, so we must fake that one
|
||||
config = &configFile{PermitTimeout: time.Minute}
|
||||
defer func() { config = nil }()
|
||||
|
||||
m := irc.MustParseMessage(":amy!amy@foo.example.com PRIVMSG #mychannel :Testing")
|
||||
if !r.allowExecuteDisableOnPermit(testLogger, m, nil, badgeCollection{}) {
|
||||
t.Error("Execution was not allowed without permit")
|
||||
}
|
||||
|
||||
timerStore.AddPermit(m.Params[0], m.User)
|
||||
if r.allowExecuteDisableOnPermit(testLogger, m, nil, badgeCollection{}) {
|
||||
t.Error("Execution was allowed with permit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteEventWhitelist(t *testing.T) {
|
||||
r := &rule{MatchEvent: func(s string) *string { return &s }("test")}
|
||||
|
||||
for evt, exp := range map[string]bool{
|
||||
"foobar": false,
|
||||
"test": true,
|
||||
} {
|
||||
if res := r.allowExecuteEventWhitelist(testLogger, nil, &evt, badgeCollection{}); exp != res {
|
||||
t.Errorf("Event %q yield unexpected result: exp=%v res=%v", evt, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteMessageMatcherBlacklist(t *testing.T) {
|
||||
r := &rule{DisableOnMatchMessages: []string{`^!disable`}}
|
||||
|
||||
for msg, exp := range map[string]bool{
|
||||
"PRIVMSG #test :Random message": true,
|
||||
"PRIVMSG #test :!disable this one": false,
|
||||
} {
|
||||
if res := r.allowExecuteMessageMatcherBlacklist(testLogger, irc.MustParseMessage(msg), nil, badgeCollection{}); exp != res {
|
||||
t.Errorf("Message %q yield unexpected result: exp=%v res=%v", msg, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteMessageMatcherWhitelist(t *testing.T) {
|
||||
r := &rule{MatchMessage: func(s string) *string { return &s }(`^!test`)}
|
||||
|
||||
for msg, exp := range map[string]bool{
|
||||
"PRIVMSG #test :Random message": false,
|
||||
"PRIVMSG #test :!test this one": true,
|
||||
} {
|
||||
if res := r.allowExecuteMessageMatcherWhitelist(testLogger, irc.MustParseMessage(msg), nil, badgeCollection{}); exp != res {
|
||||
t.Errorf("Message %q yield unexpected result: exp=%v res=%v", msg, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteUserWhitelist(t *testing.T) {
|
||||
r := &rule{MatchUsers: []string{"amy"}}
|
||||
|
||||
for msg, exp := range map[string]bool{
|
||||
":amy!amy@foo.example.com PRIVMSG #mychannel :Testing": true,
|
||||
":bob!bob@foo.example.com PRIVMSG #mychannel :Testing": false,
|
||||
} {
|
||||
if res := r.allowExecuteUserWhitelist(testLogger, irc.MustParseMessage(msg), nil, badgeCollection{}); exp != res {
|
||||
t.Errorf("Message %q yield unexpected result: exp=%v res=%v", msg, exp, res)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ type timerType uint8
|
|||
|
||||
const (
|
||||
timerTypePermit timerType = iota
|
||||
timerTypeChatMessage
|
||||
timerTypeCooldown
|
||||
)
|
||||
|
||||
|
@ -26,7 +25,6 @@ type timerEntry struct {
|
|||
type timer struct {
|
||||
timers map[string]timerEntry
|
||||
lock *sync.RWMutex
|
||||
kind timerType
|
||||
}
|
||||
|
||||
func newTimer() *timer {
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const timeDay = 24 * time.Hour
|
||||
|
||||
var twitch = newTwitchClient()
|
||||
|
||||
type twitchClient struct {
|
||||
|
@ -85,7 +87,7 @@ func (t twitchClient) GetFollowDate(from, to string) (time.Time, error) {
|
|||
}
|
||||
|
||||
// Follow date will not change that often, cache for a long time
|
||||
t.apiCache.Set(cacheKey, 24*time.Hour, payload.Data[0].FollowedAt)
|
||||
t.apiCache.Set(cacheKey, timeDay, payload.Data[0].FollowedAt)
|
||||
|
||||
return payload.Data[0].FollowedAt, nil
|
||||
}
|
||||
|
@ -154,7 +156,7 @@ func (t twitchClient) getIDForUsername(username string) (string, error) {
|
|||
}
|
||||
|
||||
// The ID for an username will not change (often), cache for a long time
|
||||
t.apiCache.Set(cacheKey, 24*time.Hour, payload.Data[0].ID)
|
||||
t.apiCache.Set(cacheKey, timeDay, payload.Data[0].ID)
|
||||
|
||||
return payload.Data[0].ID, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue