mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2025-02-07 02:52:20 +00:00
Lint: Update linter config, improve code quality
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
7189232093
commit
c78356f68f
124 changed files with 1332 additions and 1062 deletions
140
.golangci.yml
140
.golangci.yml
|
@ -12,34 +12,27 @@ output:
|
|||
format: tab
|
||||
|
||||
issues:
|
||||
# This disables the included exclude-list in golangci-lint as that
|
||||
# list for example fully hides G304 gosec rule, errcheck, exported
|
||||
# rule of revive and other errors one really wants to see.
|
||||
# Smme detail: https://github.com/golangci/golangci-lint/issues/456
|
||||
exclude-use-default: false
|
||||
# Don't limit the number of shown issues: Report ALL of them
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters-settings:
|
||||
forbidigo:
|
||||
forbid:
|
||||
- 'fmt\.Errorf' # Should use github.com/pkg/errors
|
||||
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 60
|
||||
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 15
|
||||
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false]
|
||||
- contextcheck # check the function whether use a non-inherited context [fast: false, auto-fix: false]
|
||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||
- exportloopref # checks for pointers to enclosing loop variables [fast: true, auto-fix: false]
|
||||
- forbidigo # Forbids identifiers [fast: true, auto-fix: false]
|
||||
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
|
@ -58,13 +51,124 @@ linters:
|
|||
- 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]
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, 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]
|
||||
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, 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]
|
||||
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||
- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||
|
||||
linters-settings:
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 60
|
||||
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 15
|
||||
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||
|
||||
revive:
|
||||
rules:
|
||||
#- name: add-constant # Suggests using constant for magic numbers and string literals
|
||||
# Opinion: Makes sense for strings, not for numbers but checks numbers
|
||||
#- name: argument-limit # Specifies the maximum number of arguments a function can receive | Opinion: Don't need this
|
||||
- name: atomic # Check for common mistaken usages of the `sync/atomic` package
|
||||
- name: banned-characters # Checks banned characters in identifiers
|
||||
arguments:
|
||||
- ';' # Greek question mark
|
||||
- name: bare-return # Warns on bare returns
|
||||
- name: blank-imports # Disallows blank imports
|
||||
- name: bool-literal-in-expr # Suggests removing Boolean literals from logic expressions
|
||||
- name: call-to-gc # Warns on explicit call to the garbage collector
|
||||
#- name: cognitive-complexity # Sets restriction for maximum Cognitive complexity.
|
||||
# There is a dedicated linter for this
|
||||
- name: confusing-naming # Warns on methods with names that differ only by capitalization
|
||||
- name: confusing-results # Suggests to name potentially confusing function results
|
||||
- name: constant-logical-expr # Warns on constant logical expressions
|
||||
- name: context-as-argument # `context.Context` should be the first argument of a function.
|
||||
- name: context-keys-type # Disallows the usage of basic types in `context.WithValue`.
|
||||
#- name: cyclomatic # Sets restriction for maximum Cyclomatic complexity.
|
||||
# There is a dedicated linter for this
|
||||
#- name: datarace # Spots potential dataraces
|
||||
# Is not (yet) available?
|
||||
- name: deep-exit # Looks for program exits in funcs other than `main()` or `init()`
|
||||
- name: defer # Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)
|
||||
- name: dot-imports # Forbids `.` imports.
|
||||
- name: duplicated-imports # Looks for packages that are imported two or more times
|
||||
- name: early-return # Spots if-then-else statements that can be refactored to simplify code reading
|
||||
- name: empty-block # Warns on empty code blocks
|
||||
- name: empty-lines # Warns when there are heading or trailing newlines in a block
|
||||
- name: errorf # Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`
|
||||
- name: error-naming # Naming of error variables.
|
||||
- name: error-return # The error return parameter should be last.
|
||||
- name: error-strings # Conventions around error strings.
|
||||
- name: exported # Naming and commenting conventions on exported symbols.
|
||||
arguments: ['sayRepetitiveInsteadOfStutters']
|
||||
#- name: file-header # Header which each file should have.
|
||||
# Useless without config, have no config for it
|
||||
- name: flag-parameter # Warns on boolean parameters that create a control coupling
|
||||
#- name: function-length # Warns on functions exceeding the statements or lines max
|
||||
# There is a dedicated linter for this
|
||||
#- name: function-result-limit # Specifies the maximum number of results a function can return
|
||||
# Opinion: Don't need this
|
||||
- name: get-return # Warns on getters that do not yield any result
|
||||
- name: identical-branches # Spots if-then-else statements with identical `then` and `else` branches
|
||||
- name: if-return # Redundant if when returning an error.
|
||||
#- name: imports-blacklist # Disallows importing the specified packages
|
||||
# Useless without config, have no config for it
|
||||
- name: import-shadowing # Spots identifiers that shadow an import
|
||||
- name: increment-decrement # Use `i++` and `i--` instead of `i += 1` and `i -= 1`.
|
||||
- name: indent-error-flow # Prevents redundant else statements.
|
||||
#- name: line-length-limit # Specifies the maximum number of characters in a lined
|
||||
# There is a dedicated linter for this
|
||||
#- name: max-public-structs # The maximum number of public structs in a file.
|
||||
# Opinion: Don't need this
|
||||
- name: modifies-parameter # Warns on assignments to function parameters
|
||||
- name: modifies-value-receiver # Warns on assignments to value-passed method receivers
|
||||
#- name: nested-structs # Warns on structs within structs
|
||||
# Opinion: Don't need this
|
||||
- name: optimize-operands-order # Checks inefficient conditional expressions
|
||||
#- name: package-comments # Package commenting conventions.
|
||||
# Opinion: Don't need this
|
||||
- name: range # Prevents redundant variables when iterating over a collection.
|
||||
- name: range-val-address # Warns if address of range value is used dangerously
|
||||
- name: range-val-in-closure # Warns if range value is used in a closure dispatched as goroutine
|
||||
- name: receiver-naming # Conventions around the naming of receivers.
|
||||
- name: redefines-builtin-id # Warns on redefinitions of builtin identifiers
|
||||
#- name: string-format # Warns on specific string literals that fail one or more user-configured regular expressions
|
||||
# Useless without config, have no config for it
|
||||
- name: string-of-int # Warns on suspicious casts from int to string
|
||||
- name: struct-tag # Checks common struct tags like `json`,`xml`,`yaml`
|
||||
- name: superfluous-else # Prevents redundant else statements (extends indent-error-flow)
|
||||
- name: time-equal # Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.
|
||||
- name: time-naming # Conventions around the naming of time variables.
|
||||
- name: unconditional-recursion # Warns on function calls that will lead to (direct) infinite recursion
|
||||
- name: unexported-naming # Warns on wrongly named un-exported symbols
|
||||
- name: unexported-return # Warns when a public return is from unexported type.
|
||||
- name: unhandled-error # Warns on unhandled errors returned by funcion calls
|
||||
arguments:
|
||||
- "fmt.(Fp|P)rint(f|ln|)"
|
||||
- name: unnecessary-stmt # Suggests removing or simplifying unnecessary statements
|
||||
- name: unreachable-code # Warns on unreachable code
|
||||
- name: unused-parameter # Suggests to rename or remove unused function parameters
|
||||
- name: unused-receiver # Suggests to rename or remove unused method receivers
|
||||
#- name: use-any # Proposes to replace `interface{}` with its alias `any`
|
||||
# Is not (yet) available?
|
||||
- name: useless-break # Warns on useless `break` statements in case clauses
|
||||
- name: var-declaration # Reduces redundancies around variable declaration.
|
||||
- name: var-naming # Naming rules.
|
||||
- name: waitgroup-by-value # Warns on functions taking sync.WaitGroup as a by-value parameter
|
||||
|
||||
...
|
||||
|
|
|
@ -45,8 +45,10 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
// ActorScript contains an actor to execute arbitrary commands and scripts
|
||||
type ActorScript struct{}
|
||||
|
||||
// Execute implements actor interface
|
||||
func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
command, err := attrs.StringSlice("command")
|
||||
if err != nil {
|
||||
|
@ -121,9 +123,13 @@ func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, event
|
|||
return preventCooldown, nil
|
||||
}
|
||||
|
||||
// IsAsync implements actor interface
|
||||
func (ActorScript) IsAsync() bool { return false }
|
||||
func (ActorScript) Name() string { return "script" }
|
||||
|
||||
// Name implements actor interface
|
||||
func (ActorScript) Name() string { return "script" }
|
||||
|
||||
// Validate implements actor interface
|
||||
func (ActorScript) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
cmd, err := attrs.StringSlice("command")
|
||||
if err != nil || len(cmd) == 0 {
|
||||
|
|
20
auth.go
20
auth.go
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/gofrs/uuid/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -39,7 +39,7 @@ func init() {
|
|||
},
|
||||
} {
|
||||
if err := registerRoute(rd); err != nil {
|
||||
log.WithError(err).Fatal("Unable to register auth routes")
|
||||
logrus.WithError(err).Fatal("Unable to register auth routes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,11 @@ func handleAuthUpdateBotToken(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, errors.Wrap(err, "getting access token").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
var rData twitch.OAuthTokenResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rData); err != nil {
|
||||
|
@ -79,7 +83,7 @@ func handleAuthUpdateBotToken(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_, botUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser()
|
||||
_, botUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -129,7 +133,11 @@ func handleAuthUpdateChannelGrant(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, errors.Wrap(err, "getting access token").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
var rData twitch.OAuthTokenResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rData); err != nil {
|
||||
|
@ -137,7 +145,7 @@ func handleAuthUpdateChannelGrant(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_, grantUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser()
|
||||
_, grantUser, err := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, rData.AccessToken, "").GetAuthorizedUser(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -31,7 +31,7 @@ func authBackendTwitchToken(token string) (modules []string, expiresAt time.Time
|
|||
|
||||
var httpError twitch.HTTPError
|
||||
|
||||
id, user, err := tc.GetAuthorizedUser()
|
||||
id, user, err := tc.GetAuthorizedUser(context.Background())
|
||||
switch {
|
||||
case err == nil:
|
||||
// We got a valid user, continue check below
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -71,7 +72,7 @@ func (a *autoMessage) CanSend() bool {
|
|||
}
|
||||
|
||||
if a.OnlyOnLive {
|
||||
streamLive, err := twitchClient.HasLiveStream(strings.TrimLeft(a.Channel, "#"))
|
||||
streamLive, err := twitchClient.HasLiveStream(context.Background(), strings.TrimLeft(a.Channel, "#"))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Unable to determine channel live status")
|
||||
return false
|
||||
|
|
|
@ -16,6 +16,6 @@ func getAuthorizationFromRequest(r *http.Request) (string, *twitch.Client, error
|
|||
|
||||
tc := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, token, "")
|
||||
|
||||
_, user, err := tc.GetAuthorizedUser()
|
||||
_, user, err := tc.GetAuthorizedUser(r.Context())
|
||||
return user, tc, errors.Wrap(err, "getting authorized user")
|
||||
}
|
||||
|
|
60
config.go
60
config.go
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
"github.com/gofrs/uuid/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/irc.v4"
|
||||
|
@ -23,7 +23,11 @@ import (
|
|||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
const expectedMinConfigVersion = 2
|
||||
const (
|
||||
expectedMinConfigVersion = 2
|
||||
rawLogDirPerm = 0o755
|
||||
rawLogFilePerm = 0o644
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed default_config.yaml
|
||||
|
@ -121,10 +125,10 @@ func loadConfig(filename string) error {
|
|||
if err = config.CloseRawMessageWriter(); err != nil {
|
||||
return errors.Wrap(err, "closing old raw log writer")
|
||||
}
|
||||
if err = os.MkdirAll(path.Dir(tmpConfig.RawLog), 0o755); err != nil { //nolint:gomnd // This is a common directory permission
|
||||
if err = os.MkdirAll(path.Dir(tmpConfig.RawLog), rawLogDirPerm); err != nil {
|
||||
return errors.Wrap(err, "creating directories for raw log")
|
||||
}
|
||||
if tmpConfig.rawLogWriter, err = os.OpenFile(tmpConfig.RawLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644); err != nil { //nolint:gomnd // This is a common file permission
|
||||
if tmpConfig.rawLogWriter, err = os.OpenFile(tmpConfig.RawLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, rawLogFilePerm); err != nil {
|
||||
return errors.Wrap(err, "opening raw log for appending")
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +136,7 @@ func loadConfig(filename string) error {
|
|||
config = tmpConfig
|
||||
timerService.UpdatePermitTimeout(tmpConfig.PermitTimeout)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"auto_messages": len(config.AutoMessages),
|
||||
"rules": len(config.Rules),
|
||||
"channels": len(config.Channels),
|
||||
|
@ -145,11 +149,15 @@ func loadConfig(filename string) error {
|
|||
}
|
||||
|
||||
func parseConfigFromYAML(filename string, obj interface{}, strict bool) error {
|
||||
f, err := os.Open(filename)
|
||||
f, err := os.Open(filename) //#nosec:G304 // This is intended to open a variable file
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open config file")
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing config file (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
decoder := yaml.NewDecoder(f)
|
||||
decoder.KnownFields(strict)
|
||||
|
@ -205,10 +213,13 @@ func writeConfigToYAML(filename, authorName, authorEmail, summary string, obj *c
|
|||
fmt.Fprintf(tmpFile, "# Automatically updated by %s using Config-Editor frontend, last update: %s\n", authorName, time.Now().Format(time.RFC3339))
|
||||
|
||||
if err = yaml.NewEncoder(tmpFile).Encode(obj); err != nil {
|
||||
tmpFile.Close()
|
||||
tmpFile.Close() //nolint:errcheck,gosec,revive
|
||||
return errors.Wrap(err, "encoding config")
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
if err = tmpFile.Close(); err != nil {
|
||||
return fmt.Errorf("closing temp config: %w", err)
|
||||
}
|
||||
|
||||
if err = os.Rename(tmpFileName, filename); err != nil {
|
||||
return errors.Wrap(err, "moving config to location")
|
||||
|
@ -220,7 +231,7 @@ func writeConfigToYAML(filename, authorName, authorEmail, summary string, obj *c
|
|||
|
||||
git := newGitHelper(path.Dir(filename))
|
||||
if !git.HasRepo() {
|
||||
log.Error("Instructed to track changes using Git, but config not in repo")
|
||||
logrus.Error("Instructed to track changes using Git, but config not in repo")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -231,11 +242,15 @@ func writeConfigToYAML(filename, authorName, authorEmail, summary string, obj *c
|
|||
}
|
||||
|
||||
func writeDefaultConfigFile(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
f, err := os.Create(filename) //#nosec:G304 // This is intended to open a variable file
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating config file")
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing config file (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = f.Write(defaultConfigurationYAML)
|
||||
return errors.Wrap(err, "writing default config")
|
||||
|
@ -276,11 +291,16 @@ func (c configAuthToken) validate(token string) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *configFile) CloseRawMessageWriter() error {
|
||||
func (c *configFile) CloseRawMessageWriter() (err error) {
|
||||
if c == nil || c.rawLogWriter == nil {
|
||||
return nil
|
||||
}
|
||||
return c.rawLogWriter.Close()
|
||||
|
||||
if err = c.rawLogWriter.Close(); err != nil {
|
||||
return fmt.Errorf("closing raw-log writer: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *plugins.FieldCollection) []*plugins.Rule {
|
||||
|
@ -319,14 +339,14 @@ func (configFile) fixedDuration(d time.Duration) time.Duration {
|
|||
if d > time.Second {
|
||||
return d
|
||||
}
|
||||
return d * time.Second
|
||||
return d * time.Second //nolint:durationcheck // Error is handled before
|
||||
}
|
||||
|
||||
func (configFile) fixedDurationPtr(d *time.Duration) *time.Duration {
|
||||
if d == nil || *d >= time.Second {
|
||||
return d
|
||||
}
|
||||
fd := *d * time.Second
|
||||
fd := *d * time.Second //nolint:durationcheck // Error is handled before
|
||||
return &fd
|
||||
}
|
||||
|
||||
|
@ -368,11 +388,11 @@ func (c *configFile) fixTokenHashStorage() (err error) {
|
|||
|
||||
func (c *configFile) runLoadChecks() (err error) {
|
||||
if len(c.Channels) == 0 {
|
||||
log.Warn("Loaded config with empty channel list")
|
||||
logrus.Warn("Loaded config with empty channel list")
|
||||
}
|
||||
|
||||
if len(c.Rules) == 0 {
|
||||
log.Warn("Loaded config with empty ruleset")
|
||||
logrus.Warn("Loaded config with empty ruleset")
|
||||
}
|
||||
|
||||
var seen []string
|
||||
|
@ -397,7 +417,7 @@ func (c *configFile) updateAutoMessagesFromConfig(old *configFile) {
|
|||
nam.lastMessageSent = time.Now()
|
||||
|
||||
if !nam.IsValid() {
|
||||
log.WithField("index", idx).Warn("Auto-Message configuration is invalid and therefore disabled")
|
||||
logrus.WithField("index", idx).Warn("Auto-Message configuration is invalid and therefore disabled")
|
||||
}
|
||||
|
||||
if old == nil {
|
||||
|
@ -426,7 +446,7 @@ func (c configFile) validateRuleActions() error {
|
|||
var hasError bool
|
||||
|
||||
for _, r := range c.Rules {
|
||||
logger := log.WithField("rule", r.MatcherID())
|
||||
logger := logrus.WithField("rule", r.MatcherID())
|
||||
|
||||
if err := r.Validate(validateTemplate); err != nil {
|
||||
logger.WithError(err).Error("Rule reported invalid config")
|
||||
|
|
|
@ -53,7 +53,7 @@ func registerEditorFrontend() {
|
|||
return
|
||||
}
|
||||
|
||||
io.Copy(w, f)
|
||||
io.Copy(w, f) //nolint:errcheck,gosec
|
||||
})
|
||||
|
||||
router.HandleFunc("/editor/vars.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -244,7 +244,7 @@ func configEditorHandleGeneralUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
for i := range payload.BotEditors {
|
||||
usr, err := twitchClient.GetUserInformation(payload.BotEditors[i])
|
||||
usr, err := twitchClient.GetUserInformation(r.Context(), payload.BotEditors[i])
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting bot editor profile").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -143,7 +143,7 @@ func configEditorGlobalGetModules(w http.ResponseWriter, _ *http.Request) {
|
|||
}
|
||||
|
||||
func configEditorGlobalGetUser(w http.ResponseWriter, r *http.Request) {
|
||||
usr, err := twitchClient.GetUserInformation(r.FormValue("user"))
|
||||
usr, err := twitchClient.GetUserInformation(r.Context(), r.FormValue("user"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -160,7 +160,7 @@ func configEditorGlobalSubscribe(w http.ResponseWriter, r *http.Request) {
|
|||
log.WithError(err).Error("Unable to initialize websocket")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
var (
|
||||
frontendNotify = make(chan string, 1)
|
||||
|
@ -190,7 +190,6 @@ func configEditorGlobalSubscribe(w http.ResponseWriter, r *http.Request) {
|
|||
log.WithError(err).Debug("Unable to send websocket ping")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ func configEditorRulesAdd(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if msg.SubscribeFrom != nil {
|
||||
if _, err = msg.UpdateFromSubscription(); err != nil {
|
||||
if _, err = msg.UpdateFromSubscription(r.Context()); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
|
@ -24,7 +25,7 @@ func updateConfigFromRemote() {
|
|||
for _, r := range cfg.Rules {
|
||||
logger := log.WithField("rule", r.MatcherID())
|
||||
|
||||
rhu, err := r.UpdateFromSubscription()
|
||||
rhu, err := r.UpdateFromSubscription(context.Background())
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("updating rule")
|
||||
continue
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
|
@ -78,7 +78,7 @@ func (t *templateFuncProvider) Register(name string, fg plugins.TemplateFuncGett
|
|||
defer t.lock.Unlock()
|
||||
|
||||
if _, ok := t.funcs[name]; ok {
|
||||
log.Fatalf("Duplicate registration of %q template function", name) //nolint:gocritic // Yeah, the unlock will not run but the process will end
|
||||
logrus.Fatalf("Duplicate registration of %q template function", name)
|
||||
}
|
||||
|
||||
t.funcs[name] = fg
|
||||
|
@ -108,7 +108,7 @@ func init() {
|
|||
var parts []string
|
||||
for idx, div := range []time.Duration{time.Hour, time.Minute, time.Second} {
|
||||
part := dLeft / div
|
||||
dLeft -= part * div
|
||||
dLeft -= part * div //nolint:durationcheck // One is static, this is fine
|
||||
|
||||
if len(units) <= idx || units[idx] == "" {
|
||||
continue
|
||||
|
|
2
git.go
2
git.go
|
@ -56,6 +56,6 @@ func (g gitHelper) HasRepo() bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func (g gitHelper) getSignature(name, mail string) *object.Signature {
|
||||
func (gitHelper) getSignature(name, mail string) *object.Signature {
|
||||
return &object.Signature{Name: name, Email: mail, When: time.Now()}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Package announce contains a chat essage handler to create
|
||||
// announcements from the bot
|
||||
package announce
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -16,6 +19,7 @@ var (
|
|||
announceChatcommandRegex = regexp.MustCompile(`^/announce(|blue|green|orange|purple) +(.+)$`)
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
botTwitchClient = args.GetTwitchClient()
|
||||
|
||||
|
@ -32,7 +36,7 @@ func handleChatCommand(m *irc.Message) error {
|
|||
return errors.New("announce message does not match required format")
|
||||
}
|
||||
|
||||
if err := botTwitchClient.SendChatAnnouncement(channel, matches[1], matches[2]); err != nil {
|
||||
if err := botTwitchClient.SendChatAnnouncement(context.Background(), channel, matches[1], matches[2]); err != nil {
|
||||
return errors.Wrap(err, "sending announcement")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Package ban contains actors to ban/unban users in a channel
|
||||
package ban
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
|
@ -21,7 +24,8 @@ var (
|
|||
banChatcommandRegex = regexp.MustCompile(`^/ban +([^\s]+) +(.+)$`)
|
||||
)
|
||||
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) (err error) {
|
||||
botTwitchClient = args.GetTwitchClient()
|
||||
formatMessage = args.FormatMessage
|
||||
|
||||
|
@ -45,7 +49,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
},
|
||||
})
|
||||
|
||||
args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
Description: "Executes a ban of an user in the specified channel",
|
||||
HandlerFunc: handleAPIBan,
|
||||
Method: http.MethodPost,
|
||||
|
@ -72,7 +76,9 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
Name: "user",
|
||||
},
|
||||
},
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("registering API route: %w", err)
|
||||
}
|
||||
|
||||
args.RegisterMessageModFunc("/ban", handleChatCommand)
|
||||
|
||||
|
@ -81,7 +87,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
|
||||
type actor struct{}
|
||||
|
||||
func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
ptrStringEmpty := func(v string) *string { return &v }("")
|
||||
|
||||
reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData)
|
||||
|
@ -91,6 +97,7 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
|
||||
return false, errors.Wrap(
|
||||
botTwitchClient.BanUser(
|
||||
context.Background(),
|
||||
plugins.DeriveChannel(m, eventData),
|
||||
plugins.DeriveUser(m, eventData),
|
||||
0,
|
||||
|
@ -100,10 +107,10 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
)
|
||||
}
|
||||
|
||||
func (a actor) IsAsync() bool { return false }
|
||||
func (a actor) Name() string { return actorName }
|
||||
func (actor) IsAsync() bool { return false }
|
||||
func (actor) Name() string { return actorName }
|
||||
|
||||
func (a actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
reasonTemplate, err := attrs.String("reason")
|
||||
if err != nil || reasonTemplate == "" {
|
||||
return errors.New("reason must be non-empty string")
|
||||
|
@ -124,7 +131,7 @@ func handleAPIBan(w http.ResponseWriter, r *http.Request) {
|
|||
reason = r.FormValue("reason")
|
||||
)
|
||||
|
||||
if err := botTwitchClient.BanUser(channel, user, 0, reason); err != nil {
|
||||
if err := botTwitchClient.BanUser(r.Context(), channel, user, 0, reason); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "issuing ban").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -140,7 +147,7 @@ func handleChatCommand(m *irc.Message) error {
|
|||
return errors.New("ban message does not match required format")
|
||||
}
|
||||
|
||||
if err := botTwitchClient.BanUser(channel, matches[1], 0, matches[2]); err != nil {
|
||||
if err := botTwitchClient.BanUser(context.Background(), channel, matches[1], 0, matches[2]); err != nil {
|
||||
return errors.Wrap(err, "executing ban")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package clip contains an actor to create clips on behalf of a
|
||||
// channels owner
|
||||
package clip
|
||||
|
||||
import (
|
||||
|
@ -22,6 +24,7 @@ var (
|
|||
ptrStringEmpty = func(s string) *string { return &s }("")
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
formatMessage = args.FormatMessage
|
||||
hasPerm = args.HasPermissionForChannel
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package clipdetector contains an actor to detect clip links in a
|
||||
// message and populate a template variable
|
||||
package clipdetector
|
||||
|
||||
import (
|
||||
|
@ -19,6 +21,7 @@ var (
|
|||
clipIDScanner = regexp.MustCompile(`(?:clips\.twitch\.tv|www\.twitch\.tv/[^/]*/clip)/([A-Za-z0-9_-]+)`)
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
botTwitchClient = args.GetTwitchClient()
|
||||
|
||||
|
@ -33,8 +36,10 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Actor implements the actor interface
|
||||
type Actor struct{}
|
||||
|
||||
// Execute implements the actor interface
|
||||
func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
if eventData.HasAll("clips") {
|
||||
// We already detected clips, lets not do it again
|
||||
|
@ -70,8 +75,11 @@ func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// IsAsync implements the actor interface
|
||||
func (Actor) IsAsync() bool { return false }
|
||||
|
||||
// Name implements the actor interface
|
||||
func (Actor) Name() string { return actorName }
|
||||
|
||||
// Validate implements the actor interface
|
||||
func (Actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) error { return nil }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package commercial contains an actor to run commercials in a channel
|
||||
package commercial
|
||||
|
||||
import (
|
||||
|
@ -27,6 +28,7 @@ var (
|
|||
commercialChatcommandRegex = regexp.MustCompile(`^/commercial ([0-9]+)$`)
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
formatMessage = args.FormatMessage
|
||||
permCheckFn = args.HasPermissionForChannel
|
||||
|
@ -70,10 +72,10 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
|
|||
return false, startCommercial(strings.TrimLeft(plugins.DeriveChannel(m, eventData), "#"), durationStr)
|
||||
}
|
||||
|
||||
func (a actor) IsAsync() bool { return false }
|
||||
func (a actor) Name() string { return actorName }
|
||||
func (actor) IsAsync() bool { return false }
|
||||
func (actor) Name() string { return actorName }
|
||||
|
||||
func (a actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
durationTemplate, err := attrs.String("duration")
|
||||
if err != nil || durationTemplate == "" {
|
||||
return errors.New("duration must be non-empty string")
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package counter contains actors and template functions to work with
|
||||
// database stored counters
|
||||
package counter
|
||||
|
||||
import (
|
||||
|
@ -22,20 +24,22 @@ var (
|
|||
ptrStringEmpty = func(s string) *string { return &s }("")
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
//
|
||||
//nolint:funlen // This function is a few lines too long but only contains definitions
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
func Register(args plugins.RegistrationArguments) (err error) {
|
||||
db = args.GetDatabaseConnector()
|
||||
if err := db.DB().AutoMigrate(&Counter{}); err != nil {
|
||||
if err = db.DB().AutoMigrate(&counter{}); err != nil {
|
||||
return errors.Wrap(err, "applying schema migration")
|
||||
}
|
||||
|
||||
args.RegisterCopyDatabaseFunc("counter", func(src, target *gorm.DB) error {
|
||||
return database.CopyObjects(src, target, &Counter{})
|
||||
return database.CopyObjects(src, target, &counter{}) //nolint:wrapcheck // internal helper
|
||||
})
|
||||
|
||||
formatMessage = args.FormatMessage
|
||||
|
||||
args.RegisterActor("counter", func() plugins.Actor { return &ActorCounter{} })
|
||||
args.RegisterActor("counter", func() plugins.Actor { return &actorCounter{} })
|
||||
|
||||
args.RegisterActorDocumentation(plugins.ActionDocumentation{
|
||||
Description: "Update counter values",
|
||||
|
@ -73,7 +77,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
},
|
||||
})
|
||||
|
||||
args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
Description: "Returns the (formatted) value as a plain string",
|
||||
HandlerFunc: routeActorCounterGetValue,
|
||||
Method: http.MethodGet,
|
||||
|
@ -95,9 +99,11 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
Name: "name",
|
||||
},
|
||||
},
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("registering API route: %w", err)
|
||||
}
|
||||
|
||||
args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||
Description: "Updates the value of the counter",
|
||||
HandlerFunc: routeActorCounterSetValue,
|
||||
Method: http.MethodPatch,
|
||||
|
@ -125,7 +131,9 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
Name: "name",
|
||||
},
|
||||
},
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("registering API route: %w", err)
|
||||
}
|
||||
|
||||
args.RegisterTemplateFunction("channelCounter", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
||||
return func(name string) (string, error) {
|
||||
|
@ -157,7 +165,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
},
|
||||
})
|
||||
|
||||
args.RegisterTemplateFunction("counterTopList", plugins.GenericTemplateFunctionGetter(func(prefix string, n int) ([]Counter, error) {
|
||||
args.RegisterTemplateFunction("counterTopList", plugins.GenericTemplateFunctionGetter(func(prefix string, n int) ([]counter, error) {
|
||||
return getCounterTopList(db, prefix, n)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the top n counters for the given prefix as objects with Name and Value fields",
|
||||
|
@ -169,7 +177,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
})
|
||||
|
||||
args.RegisterTemplateFunction("counterValue", plugins.GenericTemplateFunctionGetter(func(name string, _ ...string) (int64, error) {
|
||||
return GetCounterValue(db, name)
|
||||
return getCounterValue(db, name)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the current value of the counter which identifier was supplied",
|
||||
Syntax: "counterValue <counter name>",
|
||||
|
@ -185,11 +193,11 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
mod = val[0]
|
||||
}
|
||||
|
||||
if err := UpdateCounter(db, name, mod, false); err != nil {
|
||||
if err := updateCounter(db, name, mod, false); err != nil {
|
||||
return 0, errors.Wrap(err, "updating counter")
|
||||
}
|
||||
|
||||
return GetCounterValue(db, name)
|
||||
return getCounterValue(db, name)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Adds the given value (or 1 if no value) to the counter and returns its new value",
|
||||
Syntax: "counterValueAdd <counter name> [increase=1]",
|
||||
|
@ -202,9 +210,9 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type ActorCounter struct{}
|
||||
type actorCounter struct{}
|
||||
|
||||
func (a ActorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
counterName, err := formatMessage(attrs.MustString("counter", nil), m, r, eventData)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "preparing response")
|
||||
|
@ -222,7 +230,7 @@ func (a ActorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, ev
|
|||
}
|
||||
|
||||
return false, errors.Wrap(
|
||||
UpdateCounter(db, counterName, counterValue, true),
|
||||
updateCounter(db, counterName, counterValue, true),
|
||||
"set counter",
|
||||
)
|
||||
}
|
||||
|
@ -241,15 +249,15 @@ func (a ActorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, ev
|
|||
}
|
||||
|
||||
return false, errors.Wrap(
|
||||
UpdateCounter(db, counterName, counterStep, false),
|
||||
updateCounter(db, counterName, counterStep, false),
|
||||
"update counter",
|
||||
)
|
||||
}
|
||||
|
||||
func (a ActorCounter) IsAsync() bool { return false }
|
||||
func (a ActorCounter) Name() string { return "counter" }
|
||||
func (actorCounter) IsAsync() bool { return false }
|
||||
func (actorCounter) Name() string { return "counter" }
|
||||
|
||||
func (a ActorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
|
||||
if cn, err := attrs.String("counter"); err != nil || cn == "" {
|
||||
return errors.New("counter name must be non-empty string")
|
||||
}
|
||||
|
@ -269,7 +277,7 @@ func routeActorCounterGetValue(w http.ResponseWriter, r *http.Request) {
|
|||
template = "%d"
|
||||
}
|
||||
|
||||
cv, err := GetCounterValue(db, mux.Vars(r)["name"])
|
||||
cv, err := getCounterValue(db, mux.Vars(r)["name"])
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting value").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -291,7 +299,7 @@ func routeActorCounterSetValue(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = UpdateCounter(db, mux.Vars(r)["name"], value, absolute); err != nil {
|
||||
if err = updateCounter(db, mux.Vars(r)["name"], value, absolute); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "updating value").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
Counter struct {
|
||||
counter struct {
|
||||
Name string `gorm:"primaryKey"`
|
||||
Value int64
|
||||
}
|
||||
)
|
||||
|
||||
func GetCounterValue(db database.Connector, counterName string) (int64, error) {
|
||||
var c Counter
|
||||
func getCounterValue(db database.Connector, counterName string) (int64, error) {
|
||||
var c counter
|
||||
|
||||
err := helpers.Retry(func() error {
|
||||
err := db.DB().First(&c, "name = ?", counterName).Error
|
||||
|
@ -31,9 +31,10 @@ func GetCounterValue(db database.Connector, counterName string) (int64, error) {
|
|||
return c.Value, errors.Wrap(err, "querying counter")
|
||||
}
|
||||
|
||||
func UpdateCounter(db database.Connector, counterName string, value int64, absolute bool) error {
|
||||
//revive:disable-next-line:flag-parameter
|
||||
func updateCounter(db database.Connector, counterName string, value int64, absolute bool) error {
|
||||
if !absolute {
|
||||
cv, err := GetCounterValue(db, counterName)
|
||||
cv, err := getCounterValue(db, counterName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting previous value")
|
||||
}
|
||||
|
@ -46,14 +47,14 @@ func UpdateCounter(db database.Connector, counterName string, value int64, absol
|
|||
return tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "name"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"value"}),
|
||||
}).Create(Counter{Name: counterName, Value: value}).Error
|
||||
}).Create(counter{Name: counterName, Value: value}).Error
|
||||
}),
|
||||
"storing counter value",
|
||||
)
|
||||
}
|
||||
|
||||
func getCounterRank(db database.Connector, prefix, name string) (rank, count int64, err error) {
|
||||
var cc []Counter
|
||||
var cc []counter
|
||||
|
||||
if err = helpers.Retry(func() error {
|
||||
return db.DB().
|
||||
|
@ -74,8 +75,8 @@ func getCounterRank(db database.Connector, prefix, name string) (rank, count int
|
|||
return rank, count, nil
|
||||
}
|
||||
|
||||
func getCounterTopList(db database.Connector, prefix string, n int) ([]Counter, error) {
|
||||
var cc []Counter
|
||||
func getCounterTopList(db database.Connector, prefix string, n int) ([]counter, error) {
|
||||
var cc []counter
|
||||
|
||||
err := helpers.Retry(func() error {
|
||||
return db.DB().
|
||||
|
|
|
@ -12,34 +12,34 @@ import (
|
|||
|
||||
func TestCounterStoreLoop(t *testing.T) {
|
||||
dbc := database.GetTestDatabase(t)
|
||||
dbc.DB().AutoMigrate(&Counter{})
|
||||
require.NoError(t, dbc.DB().AutoMigrate(&counter{}))
|
||||
|
||||
counterName := "mytestcounter"
|
||||
|
||||
v, err := GetCounterValue(dbc, counterName)
|
||||
v, err := getCounterValue(dbc, counterName)
|
||||
assert.NoError(t, err, "reading non-existent counter")
|
||||
assert.Equal(t, int64(0), v, "expecting 0 counter value on non-existent counter")
|
||||
|
||||
err = UpdateCounter(dbc, counterName, 5, true)
|
||||
err = updateCounter(dbc, counterName, 5, true)
|
||||
assert.NoError(t, err, "inserting counter")
|
||||
|
||||
err = UpdateCounter(dbc, counterName, 1, false)
|
||||
err = updateCounter(dbc, counterName, 1, false)
|
||||
assert.NoError(t, err, "updating counter")
|
||||
|
||||
v, err = GetCounterValue(dbc, counterName)
|
||||
v, err = getCounterValue(dbc, counterName)
|
||||
assert.NoError(t, err, "reading existent counter")
|
||||
assert.Equal(t, int64(6), v, "expecting counter value on existing counter")
|
||||
}
|
||||
|
||||
func TestCounterTopListAndRank(t *testing.T) {
|
||||
dbc := database.GetTestDatabase(t)
|
||||
dbc.DB().AutoMigrate(&Counter{})
|
||||
require.NoError(t, dbc.DB().AutoMigrate(&counter{}))
|
||||
|
||||
counterTemplate := `#example:test:%v`
|
||||
for i := 0; i < 6; i++ {
|
||||
require.NoError(
|
||||
t,
|
||||
UpdateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true),
|
||||
updateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true),
|
||||
"inserting counter %d", i,
|
||||
)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ func TestCounterTopListAndRank(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Len(t, cc, 3)
|
||||
|
||||
assert.Equal(t, []Counter{
|
||||
assert.Equal(t, []counter{
|
||||
{Name: "#example:test:5", Value: 5},
|
||||
{Name: "#example:test:4", Value: 4},
|
||||
{Name: "#example:test:3", Value: 3},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package delay contains an actor to delay rule execution
|
||||
package delay
|
||||
|
||||
import (
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
|
||||
const actorName = "delay"
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
args.RegisterActor(actorName, func() plugins.Actor { return &actor{} })
|
||||
|
||||
|
@ -46,7 +48,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
|
||||
type actor struct{}
|
||||
|
||||
func (a actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
var (
|
||||
ptrZeroDuration = func(v time.Duration) *time.Duration { return &v }(0)
|
||||
delay = attrs.MustDuration("delay", ptrZeroDuration)
|
||||
|
@ -66,9 +68,9 @@ func (a actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugin
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (a actor) IsAsync() bool { return false }
|
||||
func (a actor) Name() string { return actorName }
|
||||
func (actor) IsAsync() bool { return false }
|
||||
func (actor) Name() string { return actorName }
|
||||
|
||||
func (a actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
|
||||
func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Package deleteactor contains an actor to delete messages
|
||||
package deleteactor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
|
@ -12,6 +15,7 @@ const actorName = "delete"
|
|||
|
||||
var botTwitchClient *twitch.Client
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
botTwitchClient = args.GetTwitchClient()
|
||||
|
||||
|
@ -28,7 +32,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
|
||||
type actor struct{}
|
||||
|
||||
func (a actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, _ *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, _ *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
msgID, ok := m.Tags["id"]
|
||||
if !ok || msgID == "" {
|
||||
return false, nil
|
||||
|
@ -36,6 +40,7 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData
|
|||
|
||||
return false, errors.Wrap(
|
||||
botTwitchClient.DeleteMessage(
|
||||
context.Background(),
|
||||
plugins.DeriveChannel(m, eventData),
|
||||
msgID,
|
||||
),
|
||||
|
@ -43,9 +48,9 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData
|
|||
)
|
||||
}
|
||||
|
||||
func (a actor) IsAsync() bool { return false }
|
||||
func (a actor) Name() string { return actorName }
|
||||
func (actor) IsAsync() bool { return false }
|
||||
func (actor) Name() string { return actorName }
|
||||
|
||||
func (a actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
|
||||
func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package eventmod contains an actor to modify event data during rule
|
||||