mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2025-03-15 03:07:43 +00:00
Compare commits
43 commits
Author | SHA1 | Date | |
---|---|---|---|
99eecd1631 | |||
00320ba09c | |||
db2d80642a | |||
3cfee5ccc9 | |||
9cac1686b8 | |||
f26ce9b0da | |||
dd80433cb0 | |||
0d76c58ede | |||
096657bcee | |||
ff475f286b | |||
06d7fcb019 | |||
710783aaf7 | |||
19038dbc6e | |||
740a71a173 | |||
e0a8ce3684 | |||
5a8459cedc | |||
8819b4031a | |||
41535bc4df | |||
150daf8a80 | |||
1d192ad796 | |||
b1ceb29bfb | |||
26a57c379d | |||
13bc753b7d | |||
e8d60e2733 | |||
4964ed25cf | |||
014df155ae | |||
c4be936c63 | |||
b38ecc9d0b | |||
621d266391 | |||
f1d4c1a283 | |||
0355713f7c | |||
c63793be2d | |||
2a64caec09 | |||
8e8895d32e | |||
0a37873241 | |||
19a30d342a | |||
30305600e7 | |||
5dd6a5323c | |||
a01ce9aa5f | |||
eb37a75da8 | |||
3cefd39960 | |||
ebf734be40 | |||
f56a7a3266 |
78 changed files with 3461 additions and 791 deletions
.git_changerelease.yamlirc.gomain.gopackage-lock.jsonpackage.json
.github/workflows
.golangci.ymlHistory.mdMakefileactions.goauthMiddleware.goci/workflow-parts
cli.gocli_actorDocs.gocli_apiToken.gocli_migrateDatabase.gocli_resetSecrets.gocli_tplDocs.gocli_validateConfig.goconfig.goconfigEditor_automessage.goconfigEditor_general.goconfigEditor_rules.goconfigRemoteUpdate.godocs/content
functions.gogo.modgo.suminternal
actors
apimodules
kofi
msgformat
overlays
raffle
linkcheck
locker
service/timer
template
pkg
plugins
plugins_core.goscopes.gosrc
tools
twitchWatcher.go
19
.git_changerelease.yaml
Normal file
19
.git_changerelease.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
|
||||
# Template to format the commit message containing the changelog change
|
||||
# which will be used to add the tag to.
|
||||
release_commit_message: "Release: Twitch-Bot {{.Version}}"
|
||||
|
||||
# Commands to run before committing the changelog and adding the tag.
|
||||
# Therefore these can add content to be included into the release-
|
||||
# commit. These commands have access to the `TAG_VERSION` variable
|
||||
# which contains the tag to be applied after the commit. If the
|
||||
# command specified here is prefixed with a `-` sign, the exit status
|
||||
# will not fail the release process. If it is not prefixed with a `-`
|
||||
# a non-zero exit status will terminate the release process. The
|
||||
# commands will be run from the repostory root, so sub-dirs MUST be
|
||||
# specified. All commands are run as `bash -ec "..."` so you can use
|
||||
# bash inside the commands.
|
||||
pre_commit_commands: []
|
||||
|
||||
...
|
2
.github/workflows/generated_workflow.yml
vendored
2
.github/workflows/generated_workflow.yml
vendored
|
@ -172,7 +172,7 @@ jobs:
|
|||
run: git config --global --add safe.directory /__w/twitch-bot/twitch-bot
|
||||
- name: Set up MySQL service
|
||||
run: |
|
||||
mariadb -h mysql -u root --password=root-pass <<EOF
|
||||
mariadb --skip-ssl -h mysql -u root --password=root-pass <<EOF
|
||||
CREATE DATABASE integration DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
GRANT ALL ON integration.* TO 'twitch-bot'@'%';
|
||||
EOF
|
||||
|
|
|
@ -31,11 +31,11 @@ linters:
|
|||
- 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]
|
||||
- copyloopvar # copyloopvar is a linter detects places where loop variables are copied [fast: true, 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]
|
||||
- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
|
@ -46,12 +46,12 @@ linters:
|
|||
- 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]
|
||||
- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||
- 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]
|
||||
- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||
- 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]
|
||||
|
@ -77,9 +77,7 @@ linters-settings:
|
|||
min-complexity: 15
|
||||
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||
|
||||
revive:
|
||||
rules:
|
||||
|
|
78
History.md
78
History.md
|
@ -1,3 +1,81 @@
|
|||
# 3.35.1 / 2024-12-12
|
||||
|
||||
* Bugfixes
|
||||
* [core] Fix: Reduce token requirements for category search
|
||||
* Update node dependencies
|
||||
* Update Go dependencies
|
||||
|
||||
# 3.35.0 / 2024-12-02
|
||||
|
||||
* New Features
|
||||
* [template] Add functions `parseDuration`, `parseDurationToSeconds`
|
||||
|
||||
* Bugfixes
|
||||
* [raffle] Fix: Raffle channel did not allow underscore in channel name
|
||||
|
||||
# 3.34.0 / 2024-09-16
|
||||
|
||||
* New Features
|
||||
* [marker] Implement actor to create stream markers
|
||||
* [templating] Add `currentVOD` function
|
||||
|
||||
* Bugfixes
|
||||
* [linkcheck] Fix: Replace static (deprecated) user-agent list
|
||||
|
||||
# 3.33.2 / 2024-08-27
|
||||
|
||||
* Bugfixes
|
||||
* [overlays] Fix KoFi donation currency in eventfeed
|
||||
* [raffle] Lint: Ignore linter false-positive
|
||||
* [CI] Lint: Replace deprecated linter
|
||||
|
||||
# 3.33.1 / 2024-08-14
|
||||
|
||||
* Bugfixes
|
||||
* [core] Fix: Do not execute action after permission check
|
||||
* [editor] Update dependencies
|
||||
* [raffle] Fix: Send ID as string
|
||||
|
||||
# 3.33.0 / 2024-07-27
|
||||
|
||||
* New Features
|
||||
* [overlays] Add eventfeed as default-overlay
|
||||
|
||||
* Improvements
|
||||
* [linkcheck] Add support for meta-redirects
|
||||
|
||||
* Bugfixes
|
||||
* [kofi] Fix: Use message as string
|
||||
* [overlays] Fix: Transmit event-id as string
|
||||
|
||||
# 3.32.0 / 2024-06-09
|
||||
|
||||
* New Features
|
||||
* [templating] Add `streamIsLive` function
|
||||
|
||||
* Bugfixes
|
||||
* [core] Fix: Accept proper token declaration in Authorization header
|
||||
* [core] Fix: Include username and channel in ban errors
|
||||
|
||||
# 3.31.0 / 2024-05-13
|
||||
|
||||
* Improvements
|
||||
* [core] Add locking to prevent concurrent rule executions
|
||||
|
||||
* Bugfixes
|
||||
* [spotify] Fix: Refresh-Token gets revoked when using two functions
|
||||
|
||||
# 3.30.0 / 2024-04-26
|
||||
|
||||
* New Features
|
||||
* [templating] Add `userExists` function
|
||||
|
||||
* Improvements
|
||||
* [eventsub] Suspicious user topics were moved from beta to v1
|
||||
|
||||
* Bugfixes
|
||||
* Update dependencies
|
||||
|
||||
# 3.29.2 / 2024-04-13
|
||||
|
||||
> [!IMPORTANT]
|
||||
|
|
83
Makefile
83
Makefile
|
@ -1,55 +1,80 @@
|
|||
DOCS_BASE_URL:=/
|
||||
HUGO_VERSION:=0.117.0
|
||||
|
||||
default: lint frontend_lint test
|
||||
## Tool Binaries
|
||||
GO_RUN := go run -modfile ./tools/go.mod
|
||||
GO_TEST = $(GO_RUN) gotest.tools/gotestsum --format pkgname
|
||||
GOLANCI_LINT = $(GO_RUN) github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
build_prod: frontend_prod
|
||||
##@ General
|
||||
|
||||
# The help target prints out all targets with their descriptions organized
|
||||
# beneath their categories. The categories are represented by '##@' and the
|
||||
# target descriptions by '##'. The awk commands is responsible for reading the
|
||||
# entire set of makefiles included in this invocation, looking for lines of the
|
||||
# file as xyz: ## something, and then pretty-format the target and help. Then,
|
||||
# if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||
# More info on the usage of ANSI control characters for terminal formatting:
|
||||
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
|
||||
# More info on the awk command:
|
||||
# http://linuxcommand.org/lc3_adv_awk.php
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Building
|
||||
|
||||
build_prod: frontend_prod ## Build release binary locally
|
||||
go build \
|
||||
-trimpath \
|
||||
-mod=readonly \
|
||||
-ldflags "-X main.version=$(shell git describe --tags --always || echo dev)"
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
publish: frontend_prod
|
||||
publish: frontend_prod ## Run build tooling to produce all binaries
|
||||
bash ./ci/build.sh
|
||||
|
||||
short_test:
|
||||
go test -cover -test.short -v ./...
|
||||
##@ Development
|
||||
|
||||
test:
|
||||
go test -cover -v ./...
|
||||
lint: ## Run Linter against code
|
||||
$(GOLANCI_LINT) run ./...
|
||||
|
||||
# --- Editor frontend
|
||||
short_test: ## Run tests not depending on network
|
||||
$(GO_TEST) --hide-summary skipped -- ./... -cover -short
|
||||
|
||||
test: ## Run all tests
|
||||
$(GO_TEST) --hide-summary skipped -- ./... -cover
|
||||
|
||||
##@ Editor frontend
|
||||
|
||||
frontend_prod: export NODE_ENV=production
|
||||
frontend_prod: frontend
|
||||
frontend_prod: frontend ## Build frontend in production mode
|
||||
|
||||
frontend: node_modules
|
||||
frontend: node_modules ## Build frontend
|
||||
node ci/build.mjs
|
||||
|
||||
frontend_lint: node_modules
|
||||
frontend_lint: node_modules ## Lint frontend files
|
||||
./node_modules/.bin/eslint \
|
||||
--ext .js,.vue \
|
||||
--fix \
|
||||
src
|
||||
|
||||
node_modules:
|
||||
node_modules: ## Install node modules
|
||||
npm ci --include dev
|
||||
|
||||
# --- Tools
|
||||
##@ Tooling
|
||||
|
||||
update_ua_list:
|
||||
# User-Agents provided by https://www.useragents.me/
|
||||
curl -sSf https://www.useragents.me/api | jq -r '.data[].ua' | grep -v 'Trident' >internal/linkcheck/user-agents.txt
|
||||
update-chrome-major: ## Patch latest Chrome major version into linkcheck
|
||||
sed -i -E \
|
||||
's/chromeMajor = [0-9]+/chromeMajor = $(shell curl -sSf https://lv.luzifer.io/v1/catalog/google-chrome/stable/version | cut -d '.' -f 1)/' \
|
||||
internal/linkcheck/useragent.go
|
||||
|
||||
gh-workflow:
|
||||
gh-workflow: ## Regenerate CI workflow
|
||||
bash ci/create-workflow.sh
|
||||
|
||||
# -- Vulnerability scanning --
|
||||
##@ Vulnerability scanning
|
||||
|
||||
trivy:
|
||||
trivy: ## Run Trivy against the code
|
||||
trivy fs . \
|
||||
--dependency-tree \
|
||||
--exit-code 1 \
|
||||
|
@ -58,23 +83,23 @@ trivy:
|
|||
--quiet \
|
||||
--scanners misconfig,license,secret,vuln \
|
||||
--severity HIGH,CRITICAL \
|
||||
--skip-dirs docs
|
||||
--skip-dirs docs,tools
|
||||
|
||||
# -- Documentation Site --
|
||||
##@ Documentation
|
||||
|
||||
docs: actor_docs eventclient_docs template_docs
|
||||
docs: actor_docs eventclient_docs template_docs ## Generate all documentation
|
||||
|
||||
actor_docs:
|
||||
actor_docs: ## Generate actor documentation
|
||||
go run . --storage-conn-string $(shell mktemp).db actor-docs >docs/content/configuration/actors.md
|
||||
|
||||
template_docs:
|
||||
template_docs: ## Generate template function documentation
|
||||
go run . --storage-conn-string $(shell mktemp).db tpl-docs >docs/content/configuration/templating.md
|
||||
|
||||
eventclient_docs:
|
||||
eventclient_docs: ## Generate eventclient documentation
|
||||
echo -e "---\ntitle: EventClient\nweight: 10000\n---\n" >docs/content/overlays/eventclient.md
|
||||
docker run --rm -i -v $(CURDIR):$(CURDIR) -w $(CURDIR) node:18-alpine sh -ec 'npx --yes jsdoc-to-markdown --files ./internal/apimodules/overlays/default/eventclient.js' >>docs/content/overlays/eventclient.md
|
||||
|
||||
render_docs: hugo_$(HUGO_VERSION)
|
||||
render_docs: hugo_$(HUGO_VERSION) ## Render documentation site
|
||||
./hugo_$(HUGO_VERSION) \
|
||||
--baseURL "$(DOCS_BASE_URL)" \
|
||||
--cleanDestinationDir \
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -8,6 +9,7 @@ import (
|
|||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/fieldcollection"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/locker"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
|
@ -79,6 +81,9 @@ func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *fiel
|
|||
}
|
||||
|
||||
func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection) {
|
||||
locker.LockByKey(path.Join("rule-execution", r.MatcherID()))
|
||||
defer locker.UnlockByKey(path.Join("rule-execution", r.MatcherID()))
|
||||
|
||||
var (
|
||||
ruleEventData = fieldcollection.NewFieldCollection()
|
||||
preventCooldown bool
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/uuid/v3"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -51,7 +52,25 @@ func writeAuthMiddleware(h http.Handler, module string) http.Handler {
|
|||
token = pass
|
||||
|
||||
case r.Header.Get("Authorization") != "":
|
||||
token = r.Header.Get("Authorization")
|
||||
var (
|
||||
tokenType string
|
||||
hadPrefix bool
|
||||
)
|
||||
|
||||
tokenType, token, hadPrefix = strings.Cut(r.Header.Get("Authorization"), " ")
|
||||
switch {
|
||||
case !hadPrefix:
|
||||
// Legacy: Accept `Authorization: tokenhere`
|
||||
token = tokenType
|
||||
|
||||
case strings.EqualFold(tokenType, "token"):
|
||||
// This is perfect: `Authorization: Token tokenhere`
|
||||
|
||||
default:
|
||||
// That was unexpected: `Authorization: Bearer tokenhere` or similar
|
||||
http.Error(w, "invalid token type", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
http.Error(w, "auth not successful", http.StatusForbidden)
|
||||
|
|
|
@ -43,7 +43,7 @@ steps:
|
|||
|
||||
- name: Set up MySQL service
|
||||
run: |
|
||||
mariadb -h mysql -u root --password=root-pass <<EOF
|
||||
mariadb --skip-ssl -h mysql -u root --password=root-pass <<EOF
|
||||
CREATE DATABASE integration DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
GRANT ALL ON integration.* TO 'twitch-bot'@'%';
|
||||
EOF
|
||||
|
|
82
cli.go
82
cli.go
|
@ -1,85 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
)
|
||||
|
||||
type (
|
||||
cliRegistry struct {
|
||||
cmds map[string]cliRegistryEntry
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
cliRegistryEntry struct {
|
||||
Description string
|
||||
Name string
|
||||
Params []string
|
||||
Run func([]string) error
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
cli = newCLIRegistry()
|
||||
errHelpCalled = errors.New("help called")
|
||||
)
|
||||
|
||||
func newCLIRegistry() *cliRegistry {
|
||||
return &cliRegistry{
|
||||
cmds: make(map[string]cliRegistryEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cliRegistry) Add(e cliRegistryEntry) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.cmds[e.Name] = e
|
||||
}
|
||||
|
||||
func (c *cliRegistry) Call(args []string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
cmdEntry := c.cmds[args[0]]
|
||||
if cmdEntry.Name != args[0] {
|
||||
c.help()
|
||||
return errHelpCalled
|
||||
}
|
||||
|
||||
return cmdEntry.Run(args)
|
||||
}
|
||||
|
||||
func (c *cliRegistry) help() {
|
||||
// Called from Call, does not need lock
|
||||
|
||||
var (
|
||||
maxCmdLen int
|
||||
cmds []cliRegistryEntry
|
||||
)
|
||||
|
||||
for name := range c.cmds {
|
||||
entry := c.cmds[name]
|
||||
if l := len(entry.CommandDisplay()); l > maxCmdLen {
|
||||
maxCmdLen = l
|
||||
}
|
||||
cmds = append(cmds, entry)
|
||||
}
|
||||
|
||||
sort.Slice(cmds, func(i, j int) bool { return cmds[i].Name < cmds[j].Name })
|
||||
|
||||
tpl := fmt.Sprintf(" %%-%ds %%s\n", maxCmdLen)
|
||||
fmt.Fprintln(os.Stdout, "Supported sub-commands are:")
|
||||
for _, cmd := range cmds {
|
||||
fmt.Fprintf(os.Stdout, tpl, cmd.CommandDisplay(), cmd.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func (c cliRegistryEntry) CommandDisplay() string {
|
||||
return strings.Join(append([]string{c.Name}, c.Params...), " ")
|
||||
}
|
||||
var cliTool = cli.New()
|
||||
|
|
|
@ -4,11 +4,12 @@ import (
|
|||
"bytes"
|
||||
"os"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "actor-docs",
|
||||
Description: "Generate markdown documentation for available actors",
|
||||
Run: func([]string) error {
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/gofrs/uuid/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -10,12 +11,12 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "api-token",
|
||||
Description: "Generate an api-token to be entered into the config",
|
||||
Params: []string{"<token-name>", "<scope>", "[...scope]"},
|
||||
Run: func(args []string) error {
|
||||
if len(args) < 3 { //nolint:gomnd // Just a count of parameters
|
||||
if len(args) < 3 { //nolint:mnd // Just a count of parameters
|
||||
return errors.New("Usage: twitch-bot api-token <token name> <scope> [...scope]")
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -16,12 +17,12 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "copy-database",
|
||||
Description: "Copies database contents to a new storage DSN i.e. for migrating to a new DBMS",
|
||||
Params: []string{"<target storage-type>", "<target DSN>"},
|
||||
Run: func(args []string) error {
|
||||
if len(args) < 3 { //nolint:gomnd // Just a count of parameters
|
||||
if len(args) < 3 { //nolint:mnd // Just a count of parameters
|
||||
return errors.New("Usage: twitch-bot copy-database <target storage-type> <target DSN>")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "reset-secrets",
|
||||
Description: "Remove encrypted data to reset encryption passphrase",
|
||||
Run: func([]string) error {
|
||||
|
|
|
@ -4,11 +4,12 @@ import (
|
|||
"bytes"
|
||||
"os"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "tpl-docs",
|
||||
Description: "Generate markdown documentation for available template functions",
|
||||
Run: func([]string) error {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package main
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import (
|
||||
"github.com/Luzifer/go_helpers/v2/cli"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
cliTool.Add(cli.RegistryEntry{
|
||||
Name: "validate-config",
|
||||
Description: "Try to load configuration file and report errors if any",
|
||||
Run: func([]string) error {
|
||||
|
|
|
@ -211,7 +211,9 @@ func writeConfigToYAML(filename, authorName, authorEmail, summary string, obj *c
|
|||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
|
||||
fmt.Fprintf(tmpFile, "# Automatically updated by %s using Config-Editor frontend, last update: %s\n", authorName, time.Now().Format(time.RFC3339))
|
||||
if _, err = fmt.Fprintf(tmpFile, "# Automatically updated by %s using Config-Editor frontend, last update: %s\n", authorName, time.Now().Format(time.RFC3339)); err != nil {
|
||||
return fmt.Errorf("writing file header: %w", err)
|
||||
}
|
||||
|
||||
if err = yaml.NewEncoder(tmpFile).Encode(obj); err != nil {
|
||||
tmpFile.Close() //nolint:errcheck,gosec,revive
|
||||
|
|
|
@ -81,6 +81,7 @@ func configEditorHandleAutoMessageAdd(w http.ResponseWriter, r *http.Request) {
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &autoMessage{}
|
||||
|
@ -106,6 +107,7 @@ func configEditorHandleAutoMessageDelete(w http.ResponseWriter, r *http.Request)
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := patchConfig(cfg.Config, user, "", "Delete auto-message", func(c *configFile) error {
|
||||
|
@ -142,6 +144,7 @@ func configEditorHandleAutoMessageUpdate(w http.ResponseWriter, r *http.Request)
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &autoMessage{}
|
||||
|
|
|
@ -172,6 +172,7 @@ func configEditorHandleGeneralDeleteAuthToken(w http.ResponseWriter, r *http.Req
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := patchConfig(cfg.Config, user, "", "Delete auth-token", func(cfg *configFile) error {
|
||||
|
@ -234,6 +235,7 @@ func configEditorHandleGeneralUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var payload configEditorGeneralConfig
|
||||
|
|
|
@ -81,6 +81,7 @@ func configEditorRulesAdd(w http.ResponseWriter, r *http.Request) {
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &plugins.Rule{}
|
||||
|
@ -119,6 +120,7 @@ func configEditorRulesDelete(w http.ResponseWriter, r *http.Request) {
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := patchConfig(cfg.Config, user, "", "Delete rule", func(c *configFile) error {
|
||||
|
@ -155,6 +157,7 @@ func configEditorRulesUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
user, _, err := getAuthorizationFromRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "getting authorized user").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &plugins.Rule{}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func updateConfigCron() string {
|
||||
minute := rand.Intn(60) //nolint:gomnd,gosec // Only used to distribute load
|
||||
minute := rand.Intn(60) //nolint:mnd,gosec // Only used to distribute load
|
||||
return fmt.Sprintf("0 %d * * * *", minute)
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,23 @@ Triggers the creation of a Clip from the given channel owned by the creator (sub
|
|||
add_delay: false
|
||||
```
|
||||
|
||||
## Create Marker
|
||||
|
||||
Creates a marker on the currently running stream of the given channel. The marker will be created on behalf of the channel owner and requires matching scope. (Subsequent actions can use variable `marker` to access marker details.)
|
||||
|
||||
```yaml
|
||||
- type: marker
|
||||
attributes:
|
||||
# Channel to create the marker in, defaults to the channel of the event / message
|
||||
# Optional: true
|
||||
# Type: string (Supports Templating)
|
||||
channel: ""
|
||||
# Description of the marker to create (up to 140 chars)
|
||||
# Optional: true
|
||||
# Type: string (Supports Templating)
|
||||
description: ""
|
||||
```
|
||||
|
||||
## Custom Event
|
||||
|
||||
Create a custom event
|
||||
|
@ -523,7 +540,7 @@ Send raw IRC message
|
|||
|
||||
## Send Whisper
|
||||
|
||||
Send a whisper (requires a verified bot!)
|
||||
Send a whisper
|
||||
|
||||
```yaml
|
||||
- type: whisper
|
||||
|
|
|
@ -165,6 +165,19 @@ Example:
|
|||
* 1 6
|
||||
```
|
||||
|
||||
### `currentVOD`
|
||||
|
||||
Returns the VOD of the currently running stream in the given channel (causes an error if no current stream / VOD is found)
|
||||
|
||||
Syntax: `currentVOD <username>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ currentVOD .channel }}
|
||||
* https://www.twitch.tv/videos/123456789
|
||||
```
|
||||
|
||||
### `displayName`
|
||||
|
||||
Returns the display name the specified user set for themselves
|
||||
|
@ -379,6 +392,32 @@ Example:
|
|||
< @user @user @user
|
||||
```
|
||||
|
||||
### `parseDuration`
|
||||
|
||||
Parses a duration (i.e. 1h25m10s) into a time.Duration
|
||||
|
||||
Syntax: `parseDuration <duration>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ parseDuration "1h30s" }}
|
||||
< 1h0m30s
|
||||
```
|
||||
|
||||
### `parseDurationToSeconds`
|
||||
|
||||
Parses a duration (i.e. 1h25m10s) into a number of seconds
|
||||
|
||||
Syntax: `parseDurationToSeconds <duration>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ parseDurationToSeconds "1h25m10s" }}
|
||||
< 5110
|
||||
```
|
||||
|
||||
### `pow`
|
||||
|
||||
Returns float from calculation: `float1 ** float2`
|
||||
|
@ -467,12 +506,12 @@ Example:
|
|||
|
||||
```
|
||||
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||
< Your int this hour: 88%
|
||||
< Your int this hour: 24%
|
||||
```
|
||||
|
||||
### `spotifyCurrentPlaying`
|
||||
|
||||
Retrieves the current playing track for the given channel
|
||||
Retrieves the current playing track for the given channel (returns an empty string when nothing is playing)
|
||||
|
||||
Syntax: `spotifyCurrentPlaying <channel>`
|
||||
|
||||
|
@ -487,7 +526,7 @@ Example:
|
|||
|
||||
### `spotifyLink`
|
||||
|
||||
Retrieves the link for the playing track for the given channel
|
||||
Retrieves the link for the playing track for the given channel (returns an empty string when nothing is playing)
|
||||
|
||||
Syntax: `spotifyLink <channel>`
|
||||
|
||||
|
@ -500,6 +539,19 @@ Example:
|
|||
* https://open.spotify.com/track/3HCzXf0lNpekSqsGBcGrCd
|
||||
```
|
||||
|
||||
### `streamIsLive`
|
||||
|
||||
Check whether a given channel is currently live
|
||||
|
||||
Syntax: `streamIsLive <username>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ streamIsLive "luziferus" }}
|
||||
* true
|
||||
```
|
||||
|
||||
### `streamUptime`
|
||||
|
||||
Returns the duration the stream is online (causes an error if no current stream is found)
|
||||
|
@ -567,6 +619,19 @@ Example:
|
|||
* Weather for Hamburg, DE: Few clouds with a temperature of 22 C (71.6 F). [...]
|
||||
```
|
||||
|
||||
### `userExists`
|
||||
|
||||
Checks whether the given user exists
|
||||
|
||||
Syntax: `userExists <username>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ userExists "luziferus" }}
|
||||
* true
|
||||
```
|
||||
|
||||
### `usernameForID`
|
||||
|
||||
Returns the current login name of an user-id
|
||||
|
|
|
@ -116,7 +116,7 @@ SocketMessage received for every event and passed to the new `(eventObj) => { ..
|
|||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [event_id] | <code>Number</code> | UID of the event used to re-trigger an event |
|
||||
| [event_id] | <code>String</code> | UID of the event used to re-trigger an event |
|
||||
| [is_live] | <code>Boolean</code> | Whether the event was sent through a replay (false) or occurred live (true) |
|
||||
| [reason] | <code>String</code> | Reason of this message (one of `bulk-replay`, `live-event`, `single-replay`) |
|
||||
| [time] | <code>String</code> | RFC3339 timestamp of the event |
|
||||
|
|
21
functions.go
21
functions.go
|
@ -7,21 +7,15 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/fieldcollection"
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
korvike "github.com/Luzifer/korvike/functions"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
var (
|
||||
korvikeBlacklist = []string{"now"}
|
||||
sprigBlacklist = []string{"env"}
|
||||
tplFuncs = newTemplateFuncProvider()
|
||||
)
|
||||
var tplFuncs = newTemplateFuncProvider()
|
||||
|
||||
type templateFuncProvider struct {
|
||||
docs []plugins.TemplateFuncDocumentation
|
||||
|
@ -44,16 +38,6 @@ func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, field
|
|||
|
||||
out := make(template.FuncMap)
|
||||
|
||||
for n, fn := range sprig.TxtFuncMap() {
|
||||
if str.StringInSlice(n, sprigBlacklist) {
|
||||
continue
|
||||
}
|
||||
if out[n] != nil {
|
||||
panic(fmt.Sprintf("duplicate function: %s (add in sprig)", n))
|
||||
}
|
||||
out[n] = fn
|
||||
}
|
||||
|
||||
for n, fg := range t.funcs {
|
||||
if out[n] != nil {
|
||||
panic(fmt.Sprintf("duplicate function: %s (add in registration)", n))
|
||||
|
@ -93,9 +77,6 @@ func (t *templateFuncProvider) Register(name string, fg plugins.TemplateFuncGett
|
|||
func init() {
|
||||
// Register Korvike functions
|
||||
for n, f := range korvike.GetFunctionMap() {
|
||||
if str.StringInSlice(n, korvikeBlacklist) {
|
||||
continue
|
||||
}
|
||||
tplFuncs.Register(n, plugins.GenericTemplateFunctionGetter(f))
|
||||
}
|
||||
|
||||
|
|
100
go.mod
100
go.mod
|
@ -1,77 +1,76 @@
|
|||
module github.com/Luzifer/twitch-bot/v3
|
||||
|
||||
go 1.21
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.2
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0
|
||||
github.com/Luzifer/korvike/functions v0.11.0
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/getsentry/sentry-go v0.27.0
|
||||
github.com/Luzifer/go_helpers/v2 v2.25.0
|
||||
github.com/Luzifer/korvike/functions v1.0.1
|
||||
github.com/Luzifer/rconfig/v2 v2.5.2
|
||||
github.com/getsentry/sentry-go v0.30.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/gofrs/uuid/v3 v3.1.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/itchyny/gojq v0.12.15
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/itchyny/gojq v0.12.17
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/orandin/sentrus v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.32.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
gopkg.in/irc.v4 v4.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/gorm v1.25.9
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.4.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/vault/api v1.12.2 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.5 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
||||
github.com/hashicorp/vault/api v1.15.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
|
@ -83,28 +82,25 @@ require (
|
|||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
gopkg.in/validator.v2 v2.0.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.49.0 // indirect
|
||||
modernc.org/libc v1.61.4 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.29.5 // indirect
|
||||
modernc.org/sqlite v1.34.2 // indirect
|
||||
)
|
||||
|
|
383
go.sum
383
go.sum
|
@ -1,63 +1,52 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc=
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc=
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0 h1:abACOhsn6a6c6X22jq42mZM1wuOM0Ihfa6yzssrjrOg=
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
|
||||
github.com/Luzifer/korvike/functions v0.11.0 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q=
|
||||
github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0/go.mod h1:eGWUPQeCPv/Pr/p0hjmwFgI20uqvwi/Szen69hUzGzU=
|
||||
github.com/Luzifer/go_helpers/v2 v2.25.0 h1:k1J4gd1+BfuokTDoWgcgib9P5mdadjzKEgbtKSVe46k=
|
||||
github.com/Luzifer/go_helpers/v2 v2.25.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
|
||||
github.com/Luzifer/korvike/functions v1.0.1 h1:9O9PQL7O8J3nBwR4XLyx4COC430QbnvueM+itA2HEto=
|
||||
github.com/Luzifer/korvike/functions v1.0.1/go.mod h1:8U01t/IM4wmZcaEf7u/szQrbLvBGMPOTLzJI/l7baWI=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.2 h1:4Bfp8mTrCCK/xghUmUbh/qtKiLZA6RC0tHTgqkNw1m4=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.2/go.mod h1:HnqUWg+NQh60/neUqfMDDDo5d1v8UPuhwKR1HqM4VWQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cyphar/filepath-securejoin v0.3.5 h1:L81NHjquoQmcPgXcttUS9qTSR/+bXry6pbSINQGpjj4=
|
||||
github.com/cyphar/filepath-securejoin v0.3.5/go.mod h1:edhVd3c6OXKjUmSrVa/tGJRS9joFTxlslFCAyaxigkE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
|
||||
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
|
@ -68,111 +57,72 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
|
|||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid/v3 v3.1.2 h1:V3IBv1oU82x6YIr5txe3azVHgmOKYdyKQTowm9moBlY=
|
||||
github.com/gofrs/uuid/v3 v3.1.2/go.mod h1:xPwMqoocQ1L5G6pXX5BcE7N5jlzn2o19oqAKxwZW/kI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I=
|
||||
github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
|
||||
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
||||
github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE=
|
||||
github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE=
|
||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
|
||||
github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10=
|
||||
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
|
||||
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
|
||||
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
|
||||
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
|
||||
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
|
||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
|
@ -188,77 +138,56 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/orandin/sentrus v1.0.0 h1:rMZKTUdwuhIaC7C6VbvhQPQeO9hBpliODrj7o/NmipM=
|
||||
github.com/orandin/sentrus v1.0.0/go.mod h1:Mqa1Dcat0IcuD/XPMXUolzuZ74NWptnnX8eRq3gLaSU=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0-pre.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -266,146 +195,56 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb h1:G0Rrif8QdbAz7Xy53H4Xumy6TuyKHom8pu8z/jdLwwM=
|
||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb/go.mod h1:398xiAftMV/w8frjipnUzjr/WQ+E2fnGRv9yXobxyyk=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/irc.v4 v4.0.0 h1:5jsLkU2Tg+R2nGNqmkGCrciasyi4kNkDXhyZD+C31yY=
|
||||
gopkg.in/irc.v4 v4.0.0/go.mod h1:BfjDz9MmuWW6OZY7iq4naOhudO8+QQCdO4Ko18jcsRE=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
|
||||
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
|
@ -417,25 +256,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/cc/v4 v4.19.5 h1:QlsZyQ1zf78DGeqnQ9ILi9hXyMdoC5e1qoGNUyBjHQw=
|
||||
modernc.org/cc/v4 v4.19.5/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.13.1 h1:qBttaSxEHNze36VBivw1/vkHuyjMDN3RY5wQX+p1Oxg=
|
||||
modernc.org/ccgo/v4 v4.13.1/go.mod h1:Td6RI9W9G2ZpKHaJ7UeGEiB2aIpoDqLBnm4wtkbJTbQ=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8=
|
||||
modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.23.1 h1:N49a7JiWGWV7lkPE4yYcvjkBGZQi93/JabRYjdWmJXc=
|
||||
modernc.org/ccgo/v4 v4.23.1/go.mod h1:JoIUegEIfutvoWV/BBfDFpPpfR2nc3U0jKucGcbmwDU=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.49.0 h1:/kkNBuCXvlTbOGwrQdgR67eK1Y9+kR+fhdBd89C64VM=
|
||||
modernc.org/libc v1.49.0/go.mod h1:DNz0lgQgT6FPIPm8rHtjFj0FL5/YOr/NYFXWYBcSxMw=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA=
|
||||
modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
|
@ -444,8 +281,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
|
||||
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||
modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y=
|
||||
modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
|
@ -336,7 +336,7 @@ func routeActorCounterGetValue(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text-plain")
|
||||
fmt.Fprintf(w, template, cv)
|
||||
http.Error(w, fmt.Sprintf(template, cv), http.StatusOK)
|
||||
}
|
||||
|
||||
func routeActorCounterSetValue(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
114
internal/actors/marker/actor.go
Normal file
114
internal/actors/marker/actor.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Package marker contains an actor to create markers on the current
|
||||
// running stream
|
||||
package marker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/fieldcollection"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
"gopkg.in/irc.v4"
|
||||
)
|
||||
|
||||
const actorName = "marker"
|
||||
|
||||
var (
|
||||
formatMessage plugins.MsgFormatter
|
||||
hasPerm plugins.ChannelPermissionCheckFunc
|
||||
tcGetter func(string) (*twitch.Client, error)
|
||||
)
|
||||
|
||||
// Register provides the plugins.RegisterFunc
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
formatMessage = args.FormatMessage
|
||||
hasPerm = args.HasPermissionForChannel
|
||||
tcGetter = args.GetTwitchClientForChannel
|
||||
|
||||
args.RegisterActor(actorName, func() plugins.Actor { return &actor{} })
|
||||
|
||||
args.RegisterActorDocumentation(plugins.ActionDocumentation{
|
||||
Description: "Creates a marker on the currently running stream of the given channel. The marker will be created on behalf of the channel owner and requires matching scope. (Subsequent actions can use variable `marker` to access marker details.)",
|
||||
Name: "Create Marker",
|
||||
Type: actorName,
|
||||
Fields: []plugins.ActionDocumentationField{
|
||||
{
|
||||
Description: "Channel to create the marker in, defaults to the channel of the event / message",
|
||||
Key: "channel",
|
||||
Name: "Channel",
|
||||
Optional: true,
|
||||
SupportTemplate: true,
|
||||
Type: plugins.ActionDocumentationFieldTypeString,
|
||||
},
|
||||
{
|
||||
Description: "Description of the marker to create (up to 140 chars)",
|
||||
Key: "description",
|
||||
Name: "Description",
|
||||
Optional: true,
|
||||
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) {
|
||||
channel := plugins.DeriveChannel(m, eventData)
|
||||
if channel, err = formatMessage(attrs.MustString("channel", &channel), m, r, eventData); err != nil {
|
||||
return false, fmt.Errorf("parsing channel: %w", err)
|
||||
}
|
||||
|
||||
var description string
|
||||
if description, err = formatMessage(attrs.MustString("description", &description), m, r, eventData); err != nil {
|
||||
return false, fmt.Errorf("parsing description: %w", err)
|
||||
}
|
||||
|
||||
channel = strings.TrimLeft(channel, "#")
|
||||
|
||||
canCreate, err := hasPerm(channel, twitch.ScopeChannelManageBroadcast)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("checking for required permission: %w", err)
|
||||
}
|
||||
|
||||
if !canCreate {
|
||||
return false, fmt.Errorf("creator has not given %s permission", twitch.ScopeChannelManageBroadcast)
|
||||
}
|
||||
|
||||
tc, err := tcGetter(channel)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting Twitch client for %q: %w", channel, err)
|
||||
}
|
||||
|
||||
var marker twitch.StreamMarkerInfo
|
||||
if marker, err = tc.CreateStreamMarker(context.TODO(), description); err != nil {
|
||||
return false, fmt.Errorf("creating marker: %w", err)
|
||||
}
|
||||
|
||||
eventData.Set("marker", marker)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (actor) IsAsync() bool { return false }
|
||||
|
||||
func (actor) Name() string { return actorName }
|
||||
|
||||
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
|
||||
if err = attrs.ValidateSchema(
|
||||
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
|
||||
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "description", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
|
||||
fieldcollection.MustHaveNoUnknowFields,
|
||||
helpers.SchemaValidateTemplateField(tplValidator, "channel", "description"),
|
||||
); err != nil {
|
||||
return fmt.Errorf("validating attributes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
101
internal/actors/spotify/auth.go
Normal file
101
internal/actors/spotify/auth.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package spotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/locker"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const expiryGrace = 10 * time.Second
|
||||
|
||||
func getAuthorizedClient(channel, redirectURL string) (client *http.Client, err error) {
|
||||
// In templating functions are called multiple times at once which
|
||||
// with Spotify replacing the refresh-token on each renew would kill
|
||||
// the stored token when multiple spotify functions are called at
|
||||
// once. Therefore we do have this method locking itself until it
|
||||
// has successfully made one request to the users profile and therefore
|
||||
// renewed the token. The next request then will use the token the
|
||||
// previous request renewed.
|
||||
locker.LockByKey(strings.Join([]string{"spotify", "api-access", channel}, ":"))
|
||||
defer locker.UnlockByKey(strings.Join([]string{"spotify", "api-access", channel}, ":"))
|
||||
|
||||
conf, err := oauthConfig(channel, redirectURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting oauth config: %w", err)
|
||||
}
|
||||
|
||||
var token *oauth2.Token
|
||||
if err = db.ReadEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), &token); err != nil {
|
||||
return nil, fmt.Errorf("loading oauth token: %w", err)
|
||||
}
|
||||
|
||||
ts := conf.TokenSource(context.Background(), token)
|
||||
|
||||
if token.Expiry.After(time.Now().Add(expiryGrace)) {
|
||||
// Token is still valid long enough, we spare the resources to do
|
||||
// the profile fetch and directly return the client with the token
|
||||
// as the scenario described here does not apply.
|
||||
return oauth2.NewClient(context.Background(), ts), nil
|
||||
}
|
||||
|
||||
logrus.WithField("channel", channel).Debug("refreshing spotify token")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), spotifyRequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// We do a request to /me once to refresh the token if needed
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.spotify.com/v1/me", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating currently-playing request: %w", err)
|
||||
}
|
||||
|
||||
oauthClient := oauth2.NewClient(context.Background(), ts)
|
||||
|
||||
resp, err := oauthClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing request: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing Spotify response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("requesting user profile: %w", err)
|
||||
}
|
||||
|
||||
updToken, err := ts.Token()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting updated token: %w", err)
|
||||
}
|
||||
|
||||
if err := db.StoreEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), updToken); err != nil {
|
||||
logrus.WithError(err).Error("storing back Spotify auth token")
|
||||
}
|
||||
|
||||
return oauthClient, nil
|
||||
}
|
||||
|
||||
func oauthConfig(channel, redirectURL string) (conf *oauth2.Config, err error) {
|
||||
clientID, err := getModuleConfig(actorName, channel).String("clientId")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting clientId for channel: %w", err)
|
||||
}
|
||||
|
||||
return &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.spotify.com/authorize",
|
||||
TokenURL: "https://accounts.spotify.com/api/token",
|
||||
},
|
||||
RedirectURL: redirectURL,
|
||||
Scopes: []string{"user-read-currently-playing"},
|
||||
}, nil
|
||||
}
|
|
@ -3,34 +3,25 @@ package spotify
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var errNotPlaying = errors.New("nothing playing")
|
||||
|
||||
func getCurrentTrackForChannel(channel string) (track currentPlayingTrackResponse, err error) {
|
||||
channel = strings.TrimLeft(channel, "#")
|
||||
|
||||
conf, err := oauthConfig(channel, "")
|
||||
client, err := getAuthorizedClient(channel, "")
|
||||
if err != nil {
|
||||
return track, fmt.Errorf("getting oauth config: %w", err)
|
||||
return track, fmt.Errorf("retrieving authorized Spotify client: %w", err)
|
||||
}
|
||||
|
||||
var token *oauth2.Token
|
||||
if err = db.ReadEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), &token); err != nil {
|
||||
return track, fmt.Errorf("loading oauth token: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := db.StoreEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), token); err != nil {
|
||||
logrus.WithError(err).Error("storing back Spotify auth token")
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), spotifyRequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
@ -39,7 +30,7 @@ func getCurrentTrackForChannel(channel string) (track currentPlayingTrackRespons
|
|||
return track, fmt.Errorf("creating currently-playing request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := conf.Client(context.Background(), token).Do(req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return track, fmt.Errorf("executing request: %w", err)
|
||||
}
|
||||
|
@ -58,6 +49,10 @@ func getCurrentTrackForChannel(channel string) (track currentPlayingTrackRespons
|
|||
case http.StatusOK:
|
||||
// This is perfect, continue below
|
||||
|
||||
case http.StatusNoContent:
|
||||
// User is not playing anything
|
||||
return track, errNotPlaying
|
||||
|
||||
case http.StatusUnauthorized:
|
||||
// The token is FUBAR
|
||||
return track, fmt.Errorf("token expired (HTTP 401 - unauthorized)")
|
||||
|
@ -85,6 +80,10 @@ func getCurrentTrackForChannel(channel string) (track currentPlayingTrackRespons
|
|||
func getCurrentArtistTitleForChannel(channel string) (artistTitle string, err error) {
|
||||
track, err := getCurrentTrackForChannel(channel)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotPlaying) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("getting track info: %w", err)
|
||||
}
|
||||
|
||||
|
@ -102,6 +101,10 @@ func getCurrentArtistTitleForChannel(channel string) (artistTitle string, err er
|
|||
func getCurrentLinkForChannel(channel string) (link string, err error) {
|
||||
track, err := getCurrentTrackForChannel(channel)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotPlaying) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("getting track info: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -68,22 +68,5 @@ func handleStartAuth(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "Spotify is now authorized for this channel, you can close this page")
|
||||
}
|
||||
|
||||
func oauthConfig(channel, redirectURL string) (conf *oauth2.Config, err error) {
|
||||
clientID, err := getModuleConfig(actorName, channel).String("clientId")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting clientId for channel: %w", err)
|
||||
}
|
||||
|
||||
return &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.spotify.com/authorize",
|
||||
TokenURL: "https://accounts.spotify.com/api/token",
|
||||
},
|
||||
RedirectURL: redirectURL,
|
||||
Scopes: []string{"user-read-currently-playing"},
|
||||
}, nil
|
||||
http.Error(w, "Spotify is now authorized for this channel, you can close this page", http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
|
|||
plugins.GenericTemplateFunctionGetter(getCurrentArtistTitleForChannel),
|
||||
plugins.TemplateFuncDocumentation{
|
||||
Name: "spotifyCurrentPlaying",
|
||||
Description: "Retrieves the current playing track for the given channel",
|
||||
Description: "Retrieves the current playing track for the given channel (returns an empty string when nothing is playing)",
|
||||
Syntax: "spotifyCurrentPlaying <channel>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
MatchMessage: "^!spotify",
|
||||
|
@ -51,7 +51,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
|
|||
plugins.GenericTemplateFunctionGetter(getCurrentLinkForChannel),
|
||||
plugins.TemplateFuncDocumentation{
|
||||
Name: "spotifyLink",
|
||||
Description: "Retrieves the link for the playing track for the given channel",
|
||||
Description: "Retrieves the link for the playing track for the given channel (returns an empty string when nothing is playing)",
|
||||
Syntax: "spotifyLink <channel>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
MatchMessage: "^!spotifylink",
|
||||
|
|
|
@ -195,7 +195,7 @@ func routeActorSetVarGetValue(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text-plain")
|
||||
fmt.Fprint(w, vc)
|
||||
http.Error(w, vc, http.StatusOK)
|
||||
}
|
||||
|
||||
func routeActorSetVarSetValue(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -188,7 +188,7 @@ func handleModVIP(m *irc.Message, modFn func(tc *twitch.Client, channel, user st
|
|||
channel := strings.TrimLeft(plugins.DeriveChannel(m, nil), "#")
|
||||
|
||||
parts := strings.Split(m.Trailing(), " ")
|
||||
if len(parts) != 2 { //nolint:gomnd // Just a count, makes no sense as a constant
|
||||
if len(parts) != 2 { //nolint:mnd // Just a count, makes no sense as a constant
|
||||
return errors.Errorf("wrong command usage, must consist of 2 words")
|
||||
}
|
||||
|
||||
|
|
|
@ -100,8 +100,8 @@ func handleKoFiPost(w http.ResponseWriter, r *http.Request) {
|
|||
fields.Set("isSubscription", payload.IsSubscriptionPayment)
|
||||
fields.Set("isFirstSubPayment", payload.IsFirstSubscriptionPayment)
|
||||
|
||||
if payload.IsPublic {
|
||||
fields.Set("message", payload.Message)
|
||||
if payload.IsPublic && payload.Message != nil {
|
||||
fields.Set("message", *payload.Message)
|
||||
}
|
||||
|
||||
if payload.IsSubscriptionPayment && payload.TierName != nil {
|
||||
|
|
|
@ -54,5 +54,5 @@ func handleFormattedMessage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, msg)
|
||||
http.Error(w, msg, http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
/**
|
||||
* SocketMessage received for every event and passed to the new `(eventObj) => { ... }` handlers
|
||||
* @typedef {Object} SocketMessage
|
||||
* @prop {Number} [event_id] - UID of the event used to re-trigger an event
|
||||
* @prop {String} [event_id] - UID of the event used to re-trigger an event
|
||||
* @prop {Boolean} [is_live] - Whether the event was sent through a replay (false) or occurred live (true)
|
||||
* @prop {String} [reason] - Reason of this message (one of `bulk-replay`, `live-event`, `single-replay`)
|
||||
* @prop {String} [time] - RFC3339 timestamp of the event
|
||||
|
|
19
internal/apimodules/overlays/default/eventfeed.custom.js
Normal file
19
internal/apimodules/overlays/default/eventfeed.custom.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Allows to add filters for custom events created through the customHandler
|
||||
*
|
||||
* @returns {Object} Custom filter definitions as `filterKey: {name: "Name", visible: true}`
|
||||
*/
|
||||
const customFilters = () => ({})
|
||||
|
||||
/**
|
||||
* Handles custom events and creates feed items from them
|
||||
*
|
||||
* @param {*} param0 Event-Object as returned by the websocket
|
||||
* @returns {Object} Event to add to the event list of the feed
|
||||
*/
|
||||
const customHandler = eventObj => {
|
||||
console.log('custom event unhandled:', eventObj)
|
||||
return null
|
||||
}
|
||||
|
||||
export { customFilters, customHandler }
|
156
internal/apimodules/overlays/default/eventfeed.html
Normal file
156
internal/apimodules/overlays/default/eventfeed.html
Normal file
|
@ -0,0 +1,156 @@
|
|||
<html data-bs-theme="dark">
|
||||
<head>
|
||||
<title>Event-Feed</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/combine/npm/bootstrap@5.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5/css/all.min.css">
|
||||
|
||||
<style>
|
||||
[v-cloak] { display: none; }
|
||||
.border-event {
|
||||
border-left-width: 5px !important;
|
||||
border-left-style: solid !important;
|
||||
border-left-color: #9147ff;
|
||||
}
|
||||
.border-event.event-bits { border-left-color: #5cffbe !important; }
|
||||
.border-event.event-channelpoint { border-left-color: #ffd37a !important; }
|
||||
.border-event.event-follow { border-left-color: #ff38db !important; }
|
||||
.border-event.event-raid { border-left-color: #ebeb00 !important; }
|
||||
.border-event.event-streamOffline { border-left-color: rgb(var(--bs-danger-rgb)) !important; }
|
||||
.border-event.event-subs { border-left-color: #1f69ff !important; }
|
||||
.m50 {
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.premono {
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<div class="container-fluid py-3">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<!-- Stream-Summary -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<span
|
||||
v-for="item in sortedStats"
|
||||
class="me-2 d-inline-flex align-items-center"
|
||||
:key="item.key"
|
||||
>
|
||||
<i :class="`fa-fw ${item.icon}`"></i>
|
||||
<span class="badge rounded-pill text-bg-primary ms-1">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event-List -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
Recent events
|
||||
<div class="btn-group btn-group-sm">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i class="fas fa-filter fa-fw me-1"></i>
|
||||
Filters ({{ filterCount }})
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li
|
||||
v-for="(filter, filterKey) in filters"
|
||||
:key="filterKey"
|
||||
>
|
||||
<a
|
||||
:class="{'dropdown-item': true, 'active': filter.visible}" href="#"
|
||||
@click.prevent="toggleFilterVisibility(filterKey)"
|
||||
>
|
||||
{{ filter.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="markRead"
|
||||
>
|
||||
<i class="fas fa-eye fa-fw me-1"></i>
|
||||
Mark read
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-group list-group-flush">
|
||||
|
||||
<!-- Active Hypetrain pin -->
|
||||
<div class="list-group-item" v-if="hypetrain.active">
|
||||
<div class="d-flex w-100 align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i :class="`fas fa-train fa-fw me-2`"></i>
|
||||
Hypetrain in progress towards Level {{ hypetrain.level }}…
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="progress my-3">
|
||||
<div class="progress-bar progress-bar-striped"
|
||||
:style="`width: ${(hypetrain.progress * 100).toFixed(2)}%`"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event-Item -->
|
||||
<div
|
||||
:class="eventClass(event)"
|
||||
v-for="event in recentEvents"
|
||||
:key="event.time.getTime()"
|
||||
>
|
||||
<div class="d-flex w-100 align-items-center">
|
||||
<h5 class="mb-0 me-auto"><i :class="`${event.icon} fa-fw me-2`"></i> {{ event.title }}</h5>
|
||||
<button
|
||||
class="btn btn-sm me-1"
|
||||
v-if="event.hasReplay"
|
||||
@click="repeatEvent(event.eventId)"
|
||||
title="Re-Play Event"
|
||||
>
|
||||
<i class="fas fa-share fa-fw"></i>
|
||||
</button>
|
||||
<small :title="timeDisplay(event.time)">
|
||||
{{ timeSince(event.time) }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex my-1 w-100 justify-content-between align-items-start premono" v-if="event.text">
|
||||
{{ event.text }}
|
||||
</div>
|
||||
<p class="mb-1" v-if="resolveSubtext(event.subtext)">
|
||||
<small>
|
||||
<span class="premono">{{ resolveSubtext(event.subtext) }}</span>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="eventfeed.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
585
internal/apimodules/overlays/default/eventfeed.js
Normal file
585
internal/apimodules/overlays/default/eventfeed.js
Normal file
|
@ -0,0 +1,585 @@
|
|||
/**
|
||||
* @typedef {Object} Event
|
||||
* @property {number} eventId ID of the event as returned by the server
|
||||
* @property {Object|undefined} extraData Any additional data specific to this event type
|
||||
* @property {string} filterKey Event-Type key
|
||||
* @property {string|undefined} originId ID from the Twitch server for de-duplication
|
||||
* @property {string|function|undefined} subtext Additional text, usually user-message
|
||||
* @property {string|undefined} text Descriptive text of the event
|
||||
* @property {Date} time The moment the event occurred
|
||||
* @property {string} title The title of the event
|
||||
* @property {boolean} hasReplay Whether the replay button should be shown
|
||||
* @property {boolean} isMeta Whether not to display event in frontend
|
||||
*/
|
||||
|
||||
import { customFilters, customHandler } from './eventfeed.custom.js'
|
||||
import { createApp } from 'https://cdn.jsdelivr.net/npm/vue@3.4/dist/vue.esm-browser.prod.js'
|
||||
import dayjs from 'https://cdn.jsdelivr.net/npm/dayjs@1.11/+esm'
|
||||
import dayjsLocalizedFormat from 'https://cdn.jsdelivr.net/npm/dayjs@1.11/plugin/localizedFormat.js/+esm'
|
||||
import dayjsRelativeTime from 'https://cdn.jsdelivr.net/npm/dayjs@1.11/plugin/relativeTime.js/+esm'
|
||||
import EventClient from './eventclient.mjs'
|
||||
|
||||
const STORAGE_KEY = 'io.luzifer.eventfeed'
|
||||
|
||||
const defaultFilters = {
|
||||
adbreak: { name: 'Adbreaks', visible: true },
|
||||
ban: { name: 'Bans / Timeouts', visible: true },
|
||||
bits: { name: 'Bits', visible: true },
|
||||
channelpoint: { name: 'Channel-Points', visible: true },
|
||||
donation: { name: 'Donations', visible: true },
|
||||
follow: { name: 'Follows', visible: true },
|
||||
hypetrain: { name: 'Hypetrains', visible: true },
|
||||
pollEnd: { name: 'Poll-Summary', visible: true },
|
||||
raid: { name: 'Raids', visible: true },
|
||||
shoutout: { name: 'Shoutouts', visible: true },
|
||||
streamOffline: { name: 'Stream-Offline', visible: true },
|
||||
streamUpdate: { name: 'Stream-Update', visible: true },
|
||||
subs: { name: 'Subs', visible: true },
|
||||
watchStreak: { name: 'Watchstreaks', visible: true },
|
||||
}
|
||||
|
||||
const userAnonSubgifter = 'ananonymousgifter'
|
||||
const userAnonCheerer = 'ananonymouscheerer'
|
||||
|
||||
const app = createApp({
|
||||
computed: {
|
||||
filterCount() {
|
||||
const filters = Object.values(this.filters)
|
||||
return `${filters.filter(f => f.visible).length} / ${filters.length}`
|
||||
},
|
||||
|
||||
filters() {
|
||||
return Object.fromEntries(Object.entries({
|
||||
...defaultFilters,
|
||||
...customFilters(),
|
||||
...this.storedData.filters || {},
|
||||
})
|
||||
.filter(e => Object.keys(defaultFilters).includes(e[0]) || Object.keys(customFilters()).includes(e[0]))
|
||||
.sort((a, b) => a[1].name.localeCompare(b[1].name)))
|
||||
},
|
||||
|
||||
hypetrain() {
|
||||
const evts = [...this.events]
|
||||
.filter(evt => evt.filterKey === 'hypetrain')
|
||||
.sort((b, a) => a.time.getTime() - b.time.getTime())
|
||||
|
||||
if (evts.length < 1) {
|
||||
return {
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
|
||||
return evts[0].extraData
|
||||
},
|
||||
|
||||
recentEvents() {
|
||||
return [...this.events]
|
||||
.filter(evt => !evt.isMeta)
|
||||
.filter(evt => this.filters[evt.filterKey]?.visible !== false)
|
||||
.filter(evt => !this.knownMultiGiftIDs.includes(evt.originId))
|
||||
.sort((b, a) => a.time.getTime() - b.time.getTime())
|
||||
},
|
||||
|
||||
sortedStats() {
|
||||
const evts = [...this.events]
|
||||
.filter(evt => evt.time.getTime() > this.streamOfflineTime.getTime())
|
||||
|
||||
|
||||
return [
|
||||
{
|
||||
icon: 'fas fa-gem',
|
||||
key: 'bits',
|
||||
value: evts
|
||||
.filter(evt => evt.filterKey === 'bits')
|
||||
.reduce((sum, evt) => sum + evt.extraData.bits, 0),
|
||||
},
|
||||
{
|
||||
icon: 'fas fa-circle-dollar-to-slot',
|
||||
key: 'donation',
|
||||
value: evts
|
||||
.filter(evt => evt.filterKey === 'donation')
|
||||
.reduce((sum, evt) => sum + evt.extraData.amount, 0)
|
||||
.toFixed(2),
|
||||
},
|
||||
{
|
||||
icon: 'fas fa-heart',
|
||||
key: 'follow',
|
||||
value: evts
|
||||
.filter(evt => evt.filterKey === 'follow')
|
||||
.length,
|
||||
},
|
||||
{
|
||||
icon: 'fas fa-parachute-box',
|
||||
key: 'raid',
|
||||
value: evts
|
||||
.filter(evt => evt.filterKey === 'raid')
|
||||
.length,
|
||||
},
|
||||
{
|
||||
icon: 'fas fa-star',
|
||||
key: 'sub',
|
||||
value: evts
|
||||
.filter(evt => evt.filterKey === 'subs')
|
||||
.filter(evt => !this.knownMultiGiftIDs.includes(evt.originId))
|
||||
.reduce((sum, evt) => sum + evt.extraData.count, 0),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
window.setInterval(() => {
|
||||
this.now = new Date()
|
||||
}, 60000)
|
||||
|
||||
this.eventClient = new EventClient({
|
||||
handlers: {
|
||||
adbreak_begin: ({ event_id, fields, time }) => this.handleAdBreak(event_id, fields, time),
|
||||
ban: ({ event_id, fields, time }) => this.handleBan(event_id, fields, time),
|
||||
bits: ({ event_id, fields, time }) => this.handleBits(event_id, fields, time),
|
||||
category_update: ({ event_id, fields, time }) => this.handleCategoryUpdate(event_id, fields, time),
|
||||
channelpoint_redeem: ({ event_id, fields, time }) => this.handleChannelPoints(event_id, fields, time),
|
||||
custom: eventobj => this.handleCustom(eventobj),
|
||||
follow: ({ event_id, fields, time }) => this.handleFollow(event_id, fields, time),
|
||||
hypetrain_begin: ({ event_id, fields, time }) => this.handleHypetrain(event_id, fields, time, 'start'),
|
||||
hypetrain_end: ({ event_id, fields, time }) => this.handleHypetrain(event_id, fields, time, 'end'),
|
||||
hypetrain_progress: ({ event_id, fields, time }) => this.handleHypetrain(event_id, fields, time, 'progress'),
|
||||
kofi_donation: ({ event_id, fields, time }) => this.handleKoFiDonation(event_id, fields, time),
|
||||
poll_end: ({ event_id, fields, time }) => this.handlePollEnd(event_id, fields, time),
|
||||
raid: ({ event_id, fields, time }) => this.handleRaid(event_id, fields, time),
|
||||
resub: ({ event_id, fields, reason, time, type }) => this.handleSub(type, event_id, fields, time, reason),
|
||||
shoutout_created: ({ event_id, fields, time }) => this.handleShoutoutCreated(event_id, fields, time),
|
||||
shoutout_received: ({ event_id, fields, time }) => this.handleShoutoutReceived(event_id, fields, time),
|
||||
stream_offline: ({ event_id, time }) => this.handleStreamOffline(event_id, time),
|
||||
sub: ({ event_id, fields, time, type }) => this.handleSub(type, event_id, fields, time),
|
||||
subgift: ({ event_id, fields, time, type }) => this.handleSubgift(type, event_id, fields, time),
|
||||
submysterygift: ({ event_id, fields, time, type }) => this.handleSubgift(type, event_id, fields, time),
|
||||
timeout: ({ event_id, fields, time }) => this.handleTimeout(event_id, fields, time),
|
||||
title_update: ({ event_id, fields, time }) => this.handleTitleUpdate(event_id, fields, time),
|
||||
watch_streak: ({ event_id, fields, time }) => this.handleWatchStreak(event_id, fields, time),
|
||||
},
|
||||
|
||||
maxReplayAge: 168,
|
||||
replay: true,
|
||||
})
|
||||
|
||||
this.storageLoad()
|
||||
window.addEventListener('storage', ev => {
|
||||
if (ev.key !== this.storageKey()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Our key has been changed, reload stored data
|
||||
this.storageLoad()
|
||||
})
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
eventClient: null,
|
||||
events: [],
|
||||
now: new Date(),
|
||||
storedData: {},
|
||||
|
||||
// Workaround for Twitch not sending hypetrain progress in end-event
|
||||
// eslint-disable-next-line sort-keys
|
||||
hypetrainProgress: 0,
|
||||
knownMultiGiftIDs: [],
|
||||
streamOfflineTime: new Date(0),
|
||||
subgiftRecipients: {},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
addEvent(event) {
|
||||
if (!event.eventId || !event.filterKey || !event.time || !event.title) {
|
||||
throw new Error(`Event missing fields: ${event}`)
|
||||
}
|
||||
|
||||
this.events = [
|
||||
...this.events.filter(evt => evt.eventId !== event.eventId),
|
||||
event,
|
||||
]
|
||||
},
|
||||
|
||||
eventClass(event) {
|
||||
const classes = ['border-event', 'list-group-item']
|
||||
|
||||
if (this.storedData.readDate && this.storedData.readDate > event.time.getTime()) {
|
||||
classes.push('disabled')
|
||||
}
|
||||
|
||||
if (event.filterKey) {
|
||||
classes.push(`event-${event.filterKey}`)
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
},
|
||||
|
||||
handleAdBreak(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'adbreak',
|
||||
icon: 'fas fa-rectangle-ad text-warning',
|
||||
text: `${data.duration}s ad-break is now running`,
|
||||
time: time ? new Date(time) : null,
|
||||
title: 'Ad-Break started',
|
||||
})
|
||||
},
|
||||
|
||||
handleBan(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'ban',
|
||||
icon: 'fas fa-ban',
|
||||
time: new Date(time),
|
||||
title: `${data.target_name} has been banned`,
|
||||
})
|
||||
},
|
||||
|
||||
handleBits(eventId, data, time) {
|
||||
const from = data.user === userAnonCheerer ? 'Someone' : data.user
|
||||
|
||||
this.addEvent({
|
||||
eventId,
|
||||
extraData: { bits: data.bits },
|
||||
filterKey: 'bits',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-gem',
|
||||
subtext: data.message,
|
||||
text: `${from} just spent ${data.bits} Bits`,
|
||||
time: time ? new Date(time) : null,
|
||||
title: 'Bits donated',
|
||||
})
|
||||
},
|
||||
|
||||
handleCategoryUpdate(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'streamUpdate',
|
||||
icon: 'fas fa-gamepad',
|
||||
text: data.category,
|
||||
time: new Date(time),
|
||||
title: 'Category updated',
|
||||
})
|
||||
},
|
||||
|
||||
handleChannelPoints(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'channelpoint',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-diamond',
|
||||
subtext: data.user_input,
|
||||
text: `${data.user} redeemed "${data.reward_title}"`,
|
||||
time: new Date(time),
|
||||
title: 'Reward Redeemed',
|
||||
})
|
||||
},
|
||||
|
||||
handleCustom(eventObj) {
|
||||
const evt = customHandler(eventObj)
|
||||
if (evt !== null) {
|
||||
this.addEvent(evt)
|
||||
}
|
||||
},
|
||||
|
||||
handleFollow(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'follow',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-user',
|
||||
text: `${data.user} just followed`,
|
||||
time: new Date(time),
|
||||
title: 'New Follower',
|
||||
})
|
||||
},
|
||||
|
||||
handleHypetrain(eventId, data, time, phase) {
|
||||
const evt = {
|
||||
eventId,
|
||||
extraData: {
|
||||
active: phase !== 'end',
|
||||
level: data.level,
|
||||
progress: data.levelProgress || this.hypetrainProgress,
|
||||
},
|
||||
|
||||
filterKey: 'hypetrain',
|
||||
icon: 'fas fa-train',
|
||||
time: new Date(time),
|
||||
}
|
||||
|
||||
this.hypetrainProgress = evt.extraData.progress
|
||||
|
||||
switch (phase) {
|
||||
case 'start':
|
||||
this.addEvent({
|
||||
...evt,
|
||||
text: `A hypetrain started on ${(data.levelProgress * 100).toFixed(0)}% towards level ${data.level}`,
|
||||
title: 'Hypetrain started',
|
||||
})
|
||||
break
|
||||
|
||||
case 'progress':
|
||||
this.addEvent({
|
||||
...evt,
|
||||
isMeta: true,
|
||||
title: 'Hypetrain progressed',
|
||||
})
|
||||
break
|
||||
|
||||
case 'end':
|
||||
this.addEvent({
|
||||
...evt,
|
||||
text: `A hypetrain ended on ${(this.hypetrainProgress * 100).toFixed(0)}% towards level ${data.level}`,
|
||||
title: 'Hypetrain ended',
|
||||
})
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
handleKoFiDonation(eventId, data, time) {
|
||||
let text
|
||||
if (data.isSubscription && data.isFirstSubPayment) {
|
||||
text = `${data.from} just started a monthly subscription of ${Number(data.amount).toFixed(2)} ${data.currency}`
|
||||
} else if (data.isSubscription && !data.isFirstSubPayment) {
|
||||
text = `${data.from} continued their monthly subscription of ${Number(data.amount).toFixed(2)} ${data.currency}`
|
||||
} else {
|
||||
text = `${data.from} just donated ${Number(data.amount).toFixed(2)} ${data.currency}`
|
||||
}
|
||||
|
||||
this.addEvent({
|
||||
eventId,
|
||||
extraData: { amount: Number(data.amount) },
|
||||
filterKey: 'donation',
|
||||
icon: 'fas fa-circle-dollar-to-slot',
|
||||
subtext: data.message ? data.message : undefined,
|
||||
text,
|
||||
time: new Date(time),
|
||||
title: 'Ko-fi Donation received',
|
||||
})
|
||||
},
|
||||
|
||||
handlePollEnd(eventId, data, time) {
|
||||
if (data.poll.status === 'archived') {
|
||||
return
|
||||
}
|
||||
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'pollEnd',
|
||||
icon: 'fas fa-square-poll-vertical',
|
||||
subtext: data.poll.choices.map(choice => `${choice.title} (${choice.votes})`).join(' | '),
|
||||
text: data.poll.title,
|
||||
time: new Date(time),
|
||||
title: `Poll Ended (${data.poll.status})`,
|
||||
})
|
||||
},
|
||||
|
||||
handleRaid(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'raid',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-parachute-box',
|
||||
soundUrl: '/public/fanfare.webm',
|
||||
text: `${data.from} just raided with ${data.viewercount} raiders`,
|
||||
time: new Date(time),
|
||||
title: 'Incoming raid',
|
||||
})
|
||||
},
|
||||
|
||||
handleShoutoutCreated(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'shoutout',
|
||||
icon: 'fas fa-bullhorn',
|
||||
text: `We gave a shoutout for ${data.to} to ${data.viewers} viewers`,
|
||||
time: new Date(time),
|
||||
title: 'Shoutout created',
|
||||
})
|
||||
},
|
||||
|
||||
handleShoutoutReceived(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'shoutout',
|
||||
icon: 'fas fa-bullhorn',
|
||||
text: `${data.from} just gave us a shoutout to ${data.viewers} viewers`,
|
||||
time: new Date(time),
|
||||
title: 'Shoutout received',
|
||||
})
|
||||
},
|
||||
|
||||
handleStreamOffline(eventId, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'streamOffline',
|
||||
icon: 'fas fa-clapperboard text-danger',
|
||||
time: new Date(time),
|
||||
title: 'Stream Offline',
|
||||
})
|
||||
|
||||
this.streamOfflineTime = new Date(time)
|
||||
},
|
||||
|
||||
handleSub(evt, eventId, data, time) {
|
||||
const text = evt === 'resub' ? `resubscribed for the ${data.subscribed_months}. time` : 'subscribed'
|
||||
const tier = data.plan === 'Prime' ? 'P' : `T${Number(data.plan) / 1000}`
|
||||
const title = evt === 'resub' ? `Resub (${tier})` : `New Sub (${tier})`
|
||||
this.addEvent({
|
||||
eventId,
|
||||
extraData: { count: 1 },
|
||||
filterKey: 'subs',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-star',
|
||||
subtext: data.message,
|
||||
text: `${data.user} just ${text} (${tier})`,
|
||||
time: new Date(time),
|
||||
title,
|
||||
})
|
||||
},
|
||||
|
||||
handleSubgift(evt, eventId, data, time) {
|
||||
const from = data.user === userAnonSubgifter ? 'ANON' : data.from
|
||||
|
||||
const tier = data.plan === 'Prime' ? 'Prime' : `Tier ${Number(data.plan) / 1000}`
|
||||
|
||||
if (evt === 'submysterygift') {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
extraData: { count: data.number },
|
||||
filterKey: 'subs',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-gift',
|
||||
subtext: () => this.subgiftRecipients[data.origin_id] ? `To: ${this.subgiftRecipients[data.origin_id].join(', ')}` : undefined,
|
||||
text: `${from} just gifted ${data.number} subs`,
|
||||
time: time ? new Date(time) : null,
|
||||
title: `Subs gifted (${tier})`,
|
||||
variant: 'warning',
|
||||
})
|
||||
|
||||
this.knownMultiGiftIDs.push(data.origin_id)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.origin_id) {
|
||||
this.subgiftRecipients[data.origin_id] = [
|
||||
...this.subgiftRecipients[data.origin_id] || [],
|
||||
data.to,
|
||||
].sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
|
||||
this.addEvent({
|
||||
eventId,
|
||||
extraData: { count: 1 },
|
||||
filterKey: 'subs',
|
||||
hasReplay: true,
|
||||
icon: 'fas fa-gift',
|
||||
originId: data.origin_id,
|
||||
text: `${from} just gifted ${data.to} a sub`,
|
||||
time: time ? new Date(time) : null,
|
||||
title: `Sub gifted (${tier})`,
|
||||
variant: 'warning',
|
||||
})
|
||||
},
|
||||
|
||||
handleTimeout(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'ban',
|
||||
icon: 'fas fa-ban',
|
||||
time: new Date(time),
|
||||
title: `${data.target_name} has been timed out for ${data.seconds}s`,
|
||||
})
|
||||
},
|
||||
|
||||
handleTitleUpdate(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'streamUpdate',
|
||||
icon: 'fas fa-heading',
|
||||
text: data.title,
|
||||
time: new Date(time),
|
||||
title: 'Title updated',
|
||||
})
|
||||
},
|
||||
|
||||
handleWatchStreak(eventId, data, time) {
|
||||
this.addEvent({
|
||||
eventId,
|
||||
filterKey: 'watchStreak',
|
||||
icon: 'fas fa-circle-info',
|
||||
subtext: data.message,
|
||||
text: `${data.user} watched ${data.streak} consecutive streams`,
|
||||
time: new Date(time),
|
||||
title: 'Watch-Streak shared',
|
||||
})
|
||||
},
|
||||
|
||||
markRead() {
|
||||
this.storedData.readDate = new Date().getTime()
|
||||
this.storageSave()
|
||||
},
|
||||
|
||||
repeatEvent(eventId) {
|
||||
return this.eventClient.replayEvent(eventId)
|
||||
},
|
||||
|
||||
resolveSubtext(subtext) {
|
||||
if (typeof subtext === 'function') {
|
||||
return subtext()
|
||||
}
|
||||
|
||||
return subtext
|
||||
},
|
||||
|
||||
storageKey() {
|
||||
const channel = this.eventClient.paramOptionFallback('channel').replace(/^#*/, '')
|
||||
return [STORAGE_KEY, channel].join('.')
|
||||
},
|
||||
|
||||
storageLoad() {
|
||||
this.storedData = {
|
||||
// Default values
|
||||
filters: {},
|
||||
readDate: 0,
|
||||
|
||||
// Stored data
|
||||
...JSON.parse(window.localStorage.getItem(this.storageKey()) || '{}'),
|
||||
}
|
||||
},
|
||||
|
||||
storageSave() {
|
||||
window.localStorage.setItem(this.storageKey(), JSON.stringify(this.storedData))
|
||||
},
|
||||
|
||||
timeDisplay(time) {
|
||||
return dayjs(time).format('llll')
|
||||
},
|
||||
|
||||
timeSince(time) {
|
||||
return dayjs(time).from(this.now)
|
||||
},
|
||||
|
||||
toggleFilterVisibility(filter) {
|
||||
if (!this.storedData.filters[filter]) {
|
||||
this.storedData.filters[filter] = this.filters[filter]
|
||||
}
|
||||
|
||||
this.storedData.filters[filter].visible = !this.storedData.filters[filter].visible
|
||||
this.storageSave()
|
||||
},
|
||||
},
|
||||
|
||||
name: 'EventFeed',
|
||||
})
|
||||
|
||||
dayjs.extend(dayjsLocalizedFormat)
|
||||
dayjs.extend(dayjsRelativeTime)
|
||||
|
||||
app.mount('#app')
|
|
@ -42,7 +42,7 @@ type (
|
|||
|
||||
// socketMessage represents the message overlay sockets will receive
|
||||
socketMessage struct {
|
||||
EventID uint64 `json:"event_id"`
|
||||
EventID uint64 `json:"event_id,string"`
|
||||
IsLive bool `json:"is_live"`
|
||||
Reason sendReason `json:"reason"`
|
||||
Time time.Time `json:"time"`
|
||||
|
|
|
@ -28,7 +28,7 @@ type (
|
|||
}
|
||||
|
||||
raffle struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id,string"`
|
||||
|
||||
Channel string `json:"channel"`
|
||||
Keyword string `json:"keyword"`
|
||||
|
@ -67,7 +67,7 @@ type (
|
|||
}
|
||||
|
||||
raffleEntry struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id,string"`
|
||||
RaffleID uint64 `gorm:"uniqueIndex:user_per_raffle" json:"-"`
|
||||
|
||||
UserID string `gorm:"size:128;uniqueIndex:user_per_raffle" json:"userID"`
|
||||
|
|
|
@ -57,7 +57,7 @@ func (cryptRandSrc) Int63() int64 {
|
|||
return -1
|
||||
}
|
||||
// mask off sign bit to ensure positive number
|
||||
return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
|
||||
return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1)) //#nosec:G115 - Masking ensures conversion is fine
|
||||
}
|
||||
|
||||
// We're using a non-seedable source
|
||||
|
|
|
@ -18,23 +18,23 @@ func testGenerateRaffe() raffle {
|
|||
}
|
||||
|
||||
// Now lets generate 132 non-followers taking part
|
||||
for i := 0; i < 132; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: uint64(i), Multiplier: 1})
|
||||
for i := uint64(0); i < 132; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: i, Multiplier: 1})
|
||||
}
|
||||
|
||||
// Now lets generate 500 followers taking part
|
||||
for i := 0; i < 500; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 10000 + uint64(i), Multiplier: r.MultiFollower})
|
||||
for i := uint64(0); i < 500; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 10000 + i, Multiplier: r.MultiFollower})
|
||||
}
|
||||
|
||||
// Now lets generate 200 subscribers taking part
|
||||
for i := 0; i < 200; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 20000 + uint64(i), Multiplier: r.MultiSubscriber})
|
||||
for i := uint64(0); i < 200; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 20000 + i, Multiplier: r.MultiSubscriber})
|
||||
}
|
||||
|
||||
// Now lets generate 5 VIPs taking part
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 30000 + uint64(i), Multiplier: r.MultiVIP})
|
||||
for i := uint64(0); i < 5; i++ {
|
||||
r.Entries = append(r.Entries, raffleEntry{ID: 30000 + i, Multiplier: r.MultiVIP})
|
||||
}
|
||||
|
||||
// They didn't join in order so lets shuffle them
|
||||
|
|
|
@ -57,13 +57,12 @@ func TestScanForLinks(t *testing.T) {
|
|||
t.SkipNow()
|
||||
}
|
||||
|
||||
c := New()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
Heuristic bool
|
||||
Message string
|
||||
ExpectedLinks []string
|
||||
ExpectedContains bool
|
||||
TraceStack bool
|
||||
}{
|
||||
// Case: full URL is present in the message
|
||||
{
|
||||
|
@ -183,6 +182,13 @@ func TestScanForLinks(t *testing.T) {
|
|||
{Heuristic: false, Message: "Hey btw. es kann sein, dass", ExpectedLinks: nil},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("h:%v lc:%d m:%s", testCase.Heuristic, len(testCase.ExpectedLinks), testCase.Message), func(t *testing.T) {
|
||||
var c *Checker
|
||||
if testCase.TraceStack {
|
||||
c = New(withResolver(newResolver(resolverPoolSize, withTesting(t))))
|
||||
} else {
|
||||
c = New()
|
||||
}
|
||||
|
||||
var linksFound []string
|
||||
if testCase.Heuristic {
|
||||
linksFound = c.HeuristicScanForLinks(testCase.Message)
|
||||
|
@ -209,23 +215,3 @@ func TestScanForLinks(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgentListNotEmpty(t *testing.T) {
|
||||
if len(defaultUserAgents) == 0 {
|
||||
t.Fatal("found empty user-agent list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgentRandomizer(t *testing.T) {
|
||||
uas := map[string]int{}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
uas[defaultResolver.userAgent()]++
|
||||
}
|
||||
|
||||
for _, c := range uas {
|
||||
assert.Less(t, c, 10)
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, uas[""]) // there should be no empty UA
|
||||
}
|
||||
|
|
66
internal/linkcheck/meta.go
Normal file
66
internal/linkcheck/meta.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package linkcheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoMetaRedir = fmt.Errorf("no meta-redir found")
|
||||
metaRedirContent = regexp.MustCompile(`^[0-9]+;\s*url=(.*)$`)
|
||||
)
|
||||
|
||||
//nolint:gocognit // Makes no sense to split
|
||||
func resolveMetaRedirect(body []byte) (redir string, err error) {
|
||||
tok := html.NewTokenizer(bytes.NewReader(body))
|
||||
|
||||
tokenLoop:
|
||||
for {
|
||||
token := tok.Next()
|
||||
switch token {
|
||||
case html.ErrorToken:
|
||||
if errors.Is(tok.Err(), io.EOF) {
|
||||
break tokenLoop
|
||||
}
|
||||
return "", fmt.Errorf("scanning tokens: %w", tok.Err())
|
||||
|
||||
case html.StartTagToken:
|
||||
t := tok.Token()
|
||||
if t.Data == "meta" {
|
||||
var (
|
||||
content string
|
||||
isRedirect bool
|
||||
)
|
||||
|
||||
for _, attr := range t.Attr {
|
||||
isRedirect = isRedirect || attr.Key == "http-equiv" && attr.Val == "refresh"
|
||||
|
||||
if attr.Key == "content" {
|
||||
content = attr.Val
|
||||
}
|
||||
}
|
||||
|
||||
if !isRedirect {
|
||||
continue tokenLoop
|
||||
}
|
||||
|
||||
// It is a redirect, get the content and parse it
|
||||
if matches := metaRedirContent.FindStringSubmatch(content); len(matches) > 1 {
|
||||
redir = matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if redir == "" {
|
||||
// We did not find anything
|
||||
return "", errNoMetaRedir
|
||||
}
|
||||
|
||||
return redir, nil
|
||||
}
|
41
internal/linkcheck/meta_test.go
Normal file
41
internal/linkcheck/meta_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package linkcheck
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolveMetaRedir(t *testing.T) {
|
||||
testDoc := []byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta property="twitter:image" content="">
|
||||
<meta http-equiv='refresh' content='0; url=https://github.com/Luzifer/twitch-bot'>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
redir, err := resolveMetaRedirect(testDoc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/Luzifer/twitch-bot", redir)
|
||||
|
||||
testDoc = []byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta property="twitter:image" content="">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
redir, err = resolveMetaRedirect(testDoc)
|
||||
require.ErrorIs(t, err, errNoMetaRedir)
|
||||
assert.Equal(t, "", redir)
|
||||
}
|
|
@ -2,18 +2,16 @@ package linkcheck
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
_ "embed"
|
||||
"math/big"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -30,6 +28,8 @@ type (
|
|||
resolver struct {
|
||||
resolverC chan resolverQueueEntry
|
||||
skipValidation bool
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
resolverQueueEntry struct {
|
||||
|
@ -40,20 +40,12 @@ type (
|
|||
)
|
||||
|
||||
var (
|
||||
defaultUserAgents = []string{}
|
||||
linkTest = regexp.MustCompile(`(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`)
|
||||
numericHost = regexp.MustCompile(`^(?:[0-9]+\.)*[0-9]+(?::[0-9]+)?$`)
|
||||
|
||||
//go:embed user-agents.txt
|
||||
uaList string
|
||||
linkTest = regexp.MustCompile(`(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`)
|
||||
numericHost = regexp.MustCompile(`^(?:[0-9]+\.)*[0-9]+(?::[0-9]+)?$`)
|
||||
|
||||
defaultResolver = newResolver(resolverPoolSize)
|
||||
)
|
||||
|
||||
func init() {
|
||||
defaultUserAgents = strings.Split(strings.TrimSpace(uaList), "\n")
|
||||
}
|
||||
|
||||
func newResolver(poolSize int, opts ...func(*resolver)) *resolver {
|
||||
r := &resolver{
|
||||
resolverC: make(chan resolverQueueEntry),
|
||||
|
@ -74,6 +66,10 @@ func withSkipVerify() func(*resolver) {
|
|||
return func(r *resolver) { r.skipValidation = true }
|
||||
}
|
||||
|
||||
func withTesting(t *testing.T) func(*resolver) {
|
||||
return func(r *resolver) { r.t = t }
|
||||
}
|
||||
|
||||
func (r resolver) Resolve(qe resolverQueueEntry) {
|
||||
qe.WaitGroup.Add(1)
|
||||
r.resolverC <- qe
|
||||
|
@ -87,13 +83,13 @@ func (resolver) getJar() *cookiejar.Jar {
|
|||
// resolveFinal takes a link and looks up the final destination of
|
||||
// that link after all redirects were followed
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (r resolver) resolveFinal(link string, cookieJar *cookiejar.Jar, callStack []string, userAgent string) string {
|
||||
//nolint:funlen,gocyclo
|
||||
func (r resolver) resolveFinal(link string, cookieJar *cookiejar.Jar, callStack *stack) string {
|
||||
if !linkTest.MatchString(link) && !r.skipValidation {
|
||||
return ""
|
||||
}
|
||||
|
||||
if str.StringInSlice(link, callStack) || len(callStack) == maxRedirects {
|
||||
if callStack.Count(link) > 2 || callStack.Height() == maxRedirects {
|
||||
// We got ourselves a loop: Yay!
|
||||
return link
|
||||
}
|
||||
|
@ -131,12 +127,19 @@ func (r resolver) resolveFinal(link string, cookieJar *cookiejar.Jar, callStack
|
|||
// Sanitize host: Trailing dots are valid but not required
|
||||
u.Host = strings.TrimRight(u.Host, ".")
|
||||
|
||||
if r.t != nil {
|
||||
r.t.Logf("resolving link: link=%q jar_c=%#v stack_c=%d stack_h=%d",
|
||||
link, len(cookieJar.Cookies(u)), callStack.Count(link), callStack.Height())
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
for k, v := range generateUserAgentHeaders() {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
|
@ -155,10 +158,35 @@ func (r resolver) resolveFinal(link string, cookieJar *cookiejar.Jar, callStack
|
|||
return ""
|
||||
}
|
||||
target := r.resolveReference(u, tu)
|
||||
return r.resolveFinal(target, cookieJar, append(callStack, link), userAgent)
|
||||
callStack.Visit(link)
|
||||
return r.resolveFinal(target, cookieJar, callStack)
|
||||
}
|
||||
|
||||
// We got a response, it's no redirect, we count this as a success
|
||||
// We got a response, it's no redirect, lets check for in-document stuff
|
||||
docBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if metaRedir, err := resolveMetaRedirect(docBody); err == nil {
|
||||
// Meta-Redirect found
|
||||
tu, err := url.Parse(metaRedir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
target := r.resolveReference(u, tu)
|
||||
callStack.Visit(link)
|
||||
return r.resolveFinal(target, cookieJar, callStack)
|
||||
}
|
||||
|
||||
if resp.Header.Get("Set-Cookie") != "" {
|
||||
// A new cookie was set, lets refresh the page once to see if stuff
|
||||
// changes with that new cookie
|
||||
callStack.Visit(link)
|
||||
return r.resolveFinal(u.String(), cookieJar, callStack)
|
||||
}
|
||||
|
||||
// We had no in-document redirects: we count this as a success
|
||||
return u.String()
|
||||
}
|
||||
|
||||
|
@ -201,14 +229,9 @@ func (resolver) resolveReference(origin *url.URL, loc *url.URL) string {
|
|||
|
||||
func (r resolver) runResolver() {
|
||||
for qe := range r.resolverC {
|
||||
if link := r.resolveFinal(qe.Link, r.getJar(), nil, r.userAgent()); link != "" {
|
||||
if link := r.resolveFinal(qe.Link, r.getJar(), &stack{}); link != "" {
|
||||
qe.Callback(link)
|
||||
}
|
||||
qe.WaitGroup.Done()
|
||||
}
|
||||
}
|
||||
|
||||
func (resolver) userAgent() string {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(defaultUserAgents))))
|
||||
return defaultUserAgents[n.Int64()]
|
||||
}
|
||||
|
|
27
internal/linkcheck/stack.go
Normal file
27
internal/linkcheck/stack.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package linkcheck
|
||||
|
||||
import "strings"
|
||||
|
||||
type (
|
||||
stack struct {
|
||||
visits []string
|
||||
}
|
||||
)
|
||||
|
||||
func (s stack) Count(url string) (n int) {
|
||||
for _, v := range s.visits {
|
||||
if strings.EqualFold(v, url) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (s stack) Height() int {
|
||||
return len(s.visits)
|
||||
}
|
||||
|
||||
func (s *stack) Visit(url string) {
|
||||
s.visits = append(s.visits, url)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
|
||||
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.57
|
||||
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/95.0.0.0
|
||||
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.41
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.56
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Whale/3.19.166.16 Safari/537.36
|
||||
Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76
|
||||
Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.46
|
||||
Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.192.400 QQBrowser/11.5.5250.400
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/95.0.0.0
|
||||
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0
|
||||
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763
|
||||
Mozilla/5.0 (X11; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
|
||||
Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61
|
||||
Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/110.0
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70
|
38
internal/linkcheck/useragent.go
Normal file
38
internal/linkcheck/useragent.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package linkcheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
chromeMajor = 128
|
||||
webkitMajor = 537
|
||||
webkitMinor = 36
|
||||
)
|
||||
|
||||
// generateUserAgent resembles the Chrome user agent generation as
|
||||
// closely as possible in order to blend into the crowd of browsers
|
||||
//
|
||||
// https://github.com/chromium/chromium/blob/58e23d958ee8d2bb4b085c843a18eb28b9da17da/content/common/user_agent.cc
|
||||
func generateUserAgentHeaders() map[string]string {
|
||||
return map[string]string{
|
||||
// New UA hints method
|
||||
"Sec-CH-UA": fmt.Sprintf(
|
||||
`"Chromium";v="%[1]d", "Not;A=Brand";v="24", "Google Chrome";v="%[1]d"`,
|
||||
chromeMajor,
|
||||
),
|
||||
|
||||
// Not a mobile browser
|
||||
"Sec-CH-UA-Mobile": "?0",
|
||||
|
||||
// We're always Windows
|
||||
"Sec-CH-UA-Platform": "Windows",
|
||||
|
||||
// "old" user-agent
|
||||
"User-Agent": fmt.Sprintf(
|
||||
"Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) %s Safari/537.36",
|
||||
"Windows NT 10.0; Win64; x64", // We're always Windows 10 / 11 on x64
|
||||
fmt.Sprintf("Chrome/%d.0.0.0", chromeMajor), // UA-Reduction enabled
|
||||
),
|
||||
}
|
||||
}
|
50
internal/locker/locker.go
Normal file
50
internal/locker/locker.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Package locker contains a way to interact with arbitrary locks
|
||||
package locker
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
locks = map[string]*sync.RWMutex{}
|
||||
locksOLocks sync.RWMutex
|
||||
)
|
||||
|
||||
// LockByKey takes a key to lock and locks the corresponding RWMutex
|
||||
func LockByKey(key string) { getLockByKey(key).Lock() }
|
||||
|
||||
// RLockByKey takes a key to lock and read-locks the corresponding RWMutex
|
||||
func RLockByKey(key string) { getLockByKey(key).RLock() }
|
||||
|
||||
// RUnlockByKey takes a key to lock and read-unlocks the corresponding RWMutex
|
||||
func RUnlockByKey(key string) { getLockByKey(key).RUnlock() }
|
||||
|
||||
// UnlockByKey takes a key to lock and unlocks the corresponding RWMutex
|
||||
func UnlockByKey(key string) { getLockByKey(key).Unlock() }
|
||||
|
||||
// WithLock takes a key to lock and a function to execute during the
|
||||
// lock of this key
|
||||
func WithLock(key string, fn func()) {
|
||||
LockByKey(key)
|
||||
defer UnlockByKey(key)
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
// WithRLock takes a key to lock and a function to execute during the
|
||||
// read-lock of this key
|
||||
func WithRLock(key string, fn func()) {
|
||||
RLockByKey(key)
|
||||
defer RUnlockByKey(key)
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
func getLockByKey(key string) *sync.RWMutex {
|
||||
locksOLocks.Lock()
|
||||
defer locksOLocks.Unlock()
|
||||
|
||||
if locks[key] == nil {
|
||||
locks[key] = new(sync.RWMutex)
|
||||
}
|
||||
|
||||
return locks[key]
|
||||
}
|
|
@ -72,9 +72,7 @@ func (s Service) InCooldown(tt plugins.TimerType, limiter, ruleID string) (bool,
|
|||
}
|
||||
|
||||
func (Service) getCooldownTimerKey(tt plugins.TimerType, limiter, ruleID string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%d:%s:%s", tt, limiter, ruleID)
|
||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(fmt.Sprintf("%d:%s:%s", tt, limiter, ruleID))))
|
||||
}
|
||||
|
||||
// Permit timer
|
||||
|
@ -90,9 +88,10 @@ func (s Service) HasPermit(channel, username string) (bool, error) {
|
|||
}
|
||||
|
||||
func (Service) getPermitTimerKey(channel, username string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%d:%s:%s", plugins.TimerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@")))
|
||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(fmt.Sprintf(
|
||||
"%d:%s:%s",
|
||||
plugins.TimerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@")),
|
||||
))))
|
||||
}
|
||||
|
||||
// Generic timer
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
package date
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
|
@ -27,5 +30,37 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
},
|
||||
})
|
||||
|
||||
args.RegisterTemplateFunction("parseDuration", plugins.GenericTemplateFunctionGetter(func(duration string) (time.Duration, error) {
|
||||
d, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing duration: %w", err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: `Parses a duration (i.e. 1h25m10s) into a time.Duration`,
|
||||
Syntax: "parseDuration <duration>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ parseDuration "1h30s" }}`,
|
||||
ExpectedOutput: "1h0m30s",
|
||||
},
|
||||
})
|
||||
|
||||
args.RegisterTemplateFunction("parseDurationToSeconds", plugins.GenericTemplateFunctionGetter(func(duration string) (int64, error) {
|
||||
d, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing duration: %w", err)
|
||||
}
|
||||
|
||||
return int64(d / time.Second), nil
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: `Parses a duration (i.e. 1h25m10s) into a number of seconds`,
|
||||
Syntax: "parseDurationToSeconds <duration>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ parseDurationToSeconds "1h25m10s" }}`,
|
||||
ExpectedOutput: "5110",
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -40,15 +40,15 @@ func NewInterval(a, b time.Time) (i Interval) {
|
|||
i.Seconds = u.Second() - l.Second()
|
||||
|
||||
if i.Seconds < 0 {
|
||||
i.Minutes, i.Seconds = i.Minutes-1, i.Seconds+60 //nolint:gomnd
|
||||
i.Minutes, i.Seconds = i.Minutes-1, i.Seconds+60 //nolint:mnd
|
||||
}
|
||||
|
||||
if i.Minutes < 0 {
|
||||
i.Hours, i.Minutes = i.Hours-1, i.Minutes+60 //nolint:gomnd
|
||||
i.Hours, i.Minutes = i.Hours-1, i.Minutes+60 //nolint:mnd
|
||||
}
|
||||
|
||||
if i.Hours < 0 {
|
||||
i.Days, i.Hours = i.Days-1, i.Hours+24 //nolint:gomnd
|
||||
i.Days, i.Hours = i.Days-1, i.Hours+24 //nolint:mnd
|
||||
}
|
||||
|
||||
if i.Days < 0 {
|
||||
|
@ -57,7 +57,7 @@ func NewInterval(a, b time.Time) (i Interval) {
|
|||
}
|
||||
|
||||
if i.Months < 0 {
|
||||
i.Years, i.Months = i.Years-1, i.Months+12 //nolint:gomnd
|
||||
i.Years, i.Months = i.Years-1, i.Months+12 //nolint:mnd
|
||||
}
|
||||
|
||||
return i
|
||||
|
|
|
@ -63,7 +63,7 @@ func stringToSeed(s string) (int64, error) {
|
|||
)
|
||||
|
||||
for i := 0; i < len(hashSum); i++ {
|
||||
sum += int64(float64(hashSum[len(hashSum)-1-i]%10) * math.Pow(10, float64(i))) //nolint:gomnd // No need to put the 10 of 10**i into a constant named "ten"
|
||||
sum += int64(float64(hashSum[len(hashSum)-1-i]%10) * math.Pow(10, float64(i))) //nolint:mnd // No need to put the 10 of 10**i into a constant named "ten"
|
||||
}
|
||||
|
||||
return sum, nil
|
||||
|
|
|
@ -15,6 +15,7 @@ func init() {
|
|||
regFn,
|
||||
tplTwitchRecentGame,
|
||||
tplTwitchRecentTitle,
|
||||
tplTwitchStreamIsLive,
|
||||
tplTwitchStreamUptime,
|
||||
)
|
||||
}
|
||||
|
@ -55,6 +56,20 @@ func tplTwitchRecentTitle(args plugins.RegistrationArguments) {
|
|||
})
|
||||
}
|
||||
|
||||
func tplTwitchStreamIsLive(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("streamIsLive", plugins.GenericTemplateFunctionGetter(func(username string) bool {
|
||||
_, err := args.GetTwitchClient().GetCurrentStreamInfo(context.Background(), strings.TrimLeft(username, "#"))
|
||||
return err == nil
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Check whether a given channel is currently live",
|
||||
Syntax: "streamIsLive <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ streamIsLive "luziferus" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchStreamUptime(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("streamUptime", plugins.GenericTemplateFunctionGetter(func(username string) (time.Duration, error) {
|
||||
si, err := args.GetTwitchClient().GetCurrentStreamInfo(context.Background(), strings.TrimLeft(username, "#"))
|
||||
|
|
|
@ -14,6 +14,7 @@ func init() {
|
|||
tplTwitchDisplayName,
|
||||
tplTwitchIDForUsername,
|
||||
tplTwitchProfileImage,
|
||||
tplTwitchUserExists,
|
||||
tplTwitchUsernameForID,
|
||||
)
|
||||
}
|
||||
|
@ -68,6 +69,25 @@ func tplTwitchProfileImage(args plugins.RegistrationArguments) {
|
|||
})
|
||||
}
|
||||
|
||||
func tplTwitchUserExists(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("userExists", plugins.GenericTemplateFunctionGetter(func(username string) bool {
|
||||
user, err := args.GetTwitchClient().GetUserInformation(context.Background(), strings.TrimLeft(username, "#@"))
|
||||
if err != nil {
|
||||
// Well, they probably don't exist
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.EqualFold(username, user.Login)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Checks whether the given user exists",
|
||||
Syntax: "userExists <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ userExists "luziferus" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchUsernameForID(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("usernameForID", plugins.GenericTemplateFunctionGetter(func(id string) (string, error) {
|
||||
username, err := args.GetTwitchClient().GetUsernameForID(context.Background(), id)
|
||||
|
|
50
internal/template/twitch/videos.go
Normal file
50
internal/template/twitch/videos.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
regFn = append(
|
||||
regFn,
|
||||
tplTwitchCurrentVOD,
|
||||
)
|
||||
}
|
||||
|
||||
func tplTwitchCurrentVOD(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("currentVOD", plugins.GenericTemplateFunctionGetter(func(username string) (string, error) {
|
||||
si, err := args.GetTwitchClient().GetCurrentStreamInfo(context.Background(), strings.TrimLeft(username, "#"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting stream info: %w", err)
|
||||
}
|
||||
|
||||
vids, err := args.GetTwitchClient().GetVideos(context.TODO(), twitch.GetVideoOpts{
|
||||
UserID: si.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting videos: %w", err)
|
||||
}
|
||||
|
||||
for _, v := range vids {
|
||||
if v.StreamID == nil || *v.StreamID != si.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
return v.URL, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no matching VOD found")
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the VOD of the currently running stream in the given channel (causes an error if no current stream / VOD is found)",
|
||||
Syntax: "currentVOD <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ currentVOD .channel }}`,
|
||||
FakedOutput: "https://www.twitch.tv/videos/123456789",
|
||||
},
|
||||
})
|
||||
}
|
2
irc.go
2
irc.go
|
@ -294,7 +294,7 @@ func (i ircHandler) handlePermit(m *irc.Message) {
|
|||
}
|
||||
|
||||
msgParts := strings.Split(m.Trailing(), " ")
|
||||
if len(msgParts) != 2 { //nolint:gomnd // This is not a magic number but just an expected count
|
||||
if len(msgParts) != 2 { //nolint:mnd // This is not a magic number but just an expected count
|
||||
return
|
||||
}
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -193,7 +193,7 @@ func main() {
|
|||
}
|
||||
|
||||
if len(rconfig.Args()) > 1 {
|
||||
if err = cli.Call(rconfig.Args()[1:]); err != nil {
|
||||
if err = cliTool.Call(rconfig.Args()[1:]); err != nil {
|
||||
log.Fatalf("error in command: %s", err)
|
||||
}
|
||||
return
|
||||
|
|
291
package-lock.json
generated
291
package-lock.json
generated
|
@ -4,7 +4,6 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "twitch-bot",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
|
@ -16,7 +15,7 @@
|
|||
"bootswatch": "^4.6.2",
|
||||
"codejar": "^3.7.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"vue": "^2.7.14",
|
||||
"vue": "^2.7.16",
|
||||
"vue-router": "^3.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -345,19 +344,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true,
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
|
@ -403,9 +402,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
|
||||
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.2"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
|
@ -451,13 +454,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
|
||||
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1184,13 +1187,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
|
||||
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
|
||||
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"postcss": "^8.4.14",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"prettier": "^1.18.2 || ^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/component-compiler": {
|
||||
|
@ -1344,10 +1350,11 @@
|
|||
"optional": true
|
||||
},
|
||||
"node_modules/assert-never": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz",
|
||||
"integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.3.0.tgz",
|
||||
"integrity": "sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
|
@ -1369,11 +1376,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
|
@ -1383,6 +1391,7 @@
|
|||
"resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
|
||||
"integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.9.6"
|
||||
|
@ -1496,13 +1505,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
@ -1740,10 +1750,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
@ -1867,6 +1878,7 @@
|
|||
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
|
||||
"integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
|
@ -2286,10 +2298,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
|
@ -2334,15 +2347,16 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
|
@ -2737,6 +2751,7 @@
|
|||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
|
@ -2815,6 +2830,7 @@
|
|||
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
||||
"integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
|
@ -3072,15 +3088,16 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
|
@ -3575,7 +3592,6 @@
|
|||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
|
@ -3624,13 +3640,14 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/pug": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz",
|
||||
"integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz",
|
||||
"integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"pug-code-gen": "^3.0.2",
|
||||
"pug-code-gen": "^3.0.3",
|
||||
"pug-filters": "^4.0.0",
|
||||
"pug-lexer": "^5.0.1",
|
||||
"pug-linker": "^4.0.0",
|
||||
|
@ -3645,6 +3662,7 @@
|
|||
"resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz",
|
||||
"integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"constantinople": "^4.0.1",
|
||||
|
@ -3653,27 +3671,29 @@
|
|||
}
|
||||
},
|
||||
"node_modules/pug-code-gen": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz",
|
||||
"integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz",
|
||||
"integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"constantinople": "^4.0.1",
|
||||
"doctypes": "^1.1.0",
|
||||
"js-stringify": "^1.0.2",
|
||||
"pug-attrs": "^3.0.0",
|
||||
"pug-error": "^2.0.0",
|
||||
"pug-runtime": "^3.0.0",
|
||||
"pug-error": "^2.1.0",
|
||||
"pug-runtime": "^3.0.1",
|
||||
"void-elements": "^3.1.0",
|
||||
"with": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pug-error": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz",
|
||||
"integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
|
||||
"integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pug-filters": {
|
||||
|
@ -3740,6 +3760,7 @@
|
|||
"resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz",
|
||||
"integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pug-strip-comments": {
|
||||
|
@ -4134,7 +4155,6 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
|
@ -4144,6 +4164,7 @@
|
|||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
|
@ -4250,17 +4271,20 @@
|
|||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
|
||||
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
|
||||
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
|
||||
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": "2.7.14",
|
||||
"@vue/compiler-sfc": "2.7.16",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
},
|
||||
|
@ -4308,10 +4332,11 @@
|
|||
"integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ=="
|
||||
},
|
||||
"node_modules/vue-template-compiler": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
||||
"integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
|
@ -4357,6 +4382,7 @@
|
|||
"resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
|
||||
"integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.9.6",
|
||||
|
@ -4651,16 +4677,14 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.22.5",
|
||||
|
@ -4694,9 +4718,12 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
|
||||
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.25.2"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.22.15",
|
||||
|
@ -4730,13 +4757,12 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
|
||||
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -5162,12 +5188,13 @@
|
|||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
|
||||
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
|
||||
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^1.18.2 || ^2.0.0",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
|
@ -5288,9 +5315,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"assert-never": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz",
|
||||
"integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.3.0.tgz",
|
||||
"integrity": "sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -5307,11 +5334,11 @@
|
|||
"optional": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
|
@ -5397,13 +5424,13 @@
|
|||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
|
@ -5579,9 +5606,9 @@
|
|||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
|
@ -6003,9 +6030,9 @@
|
|||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -6039,9 +6066,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
|
@ -6606,9 +6633,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="
|
||||
},
|
||||
"native-request": {
|
||||
"version": "1.1.0",
|
||||
|
@ -6975,7 +7002,6 @@
|
|||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"prismjs": {
|
||||
|
@ -7012,13 +7038,13 @@
|
|||
"dev": true
|
||||
},
|
||||
"pug": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz",
|
||||
"integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz",
|
||||
"integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"pug-code-gen": "^3.0.2",
|
||||
"pug-code-gen": "^3.0.3",
|
||||
"pug-filters": "^4.0.0",
|
||||
"pug-lexer": "^5.0.1",
|
||||
"pug-linker": "^4.0.0",
|
||||
|
@ -7041,9 +7067,9 @@
|
|||
}
|
||||
},
|
||||
"pug-code-gen": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz",
|
||||
"integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz",
|
||||
"integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -7051,16 +7077,16 @@
|
|||
"doctypes": "^1.1.0",
|
||||
"js-stringify": "^1.0.2",
|
||||
"pug-attrs": "^3.0.0",
|
||||
"pug-error": "^2.0.0",
|
||||
"pug-runtime": "^3.0.0",
|
||||
"pug-error": "^2.1.0",
|
||||
"pug-runtime": "^3.0.1",
|
||||
"void-elements": "^3.1.0",
|
||||
"with": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"pug-error": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz",
|
||||
"integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
|
||||
"integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -7412,8 +7438,7 @@
|
|||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
|
@ -7500,11 +7525,11 @@
|
|||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
|
||||
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
|
||||
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
|
||||
"requires": {
|
||||
"@vue/compiler-sfc": "2.7.14",
|
||||
"@vue/compiler-sfc": "2.7.16",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
},
|
||||
|
@ -7542,9 +7567,9 @@
|
|||
"integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ=="
|
||||
},
|
||||
"vue-template-compiler": {
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
||||
"integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"de-indent": "^1.0.2",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"bootswatch": "^4.6.2",
|
||||
"codejar": "^3.7.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"vue": "^2.7.14",
|
||||
"vue": "^2.7.16",
|
||||
"vue-router": "^3.6.5"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,12 +78,9 @@ func (c connector) StoreEncryptedCoreMeta(key string, value any) error {
|
|||
}
|
||||
|
||||
func (c connector) ValidateEncryption() error {
|
||||
validationHasher := sha512.New()
|
||||
fmt.Fprint(validationHasher, c.encryptionSecret)
|
||||
|
||||
var (
|
||||
storedHash string
|
||||
validationHash = fmt.Sprintf("%x", validationHasher.Sum(nil))
|
||||
validationHash = fmt.Sprintf("%x", sha512.Sum512([]byte(c.encryptionSecret)))
|
||||
)
|
||||
|
||||
err := backoff.NewBackoff().
|
||||
|
|
|
@ -21,10 +21,10 @@ func NewLogrusLogWriterWithLevel(logger *logrus.Logger, level logrus.Level, dbDr
|
|||
|
||||
// Print implements the gorm.Logger interface
|
||||
func (l LogWriter) Print(a ...any) {
|
||||
fmt.Fprint(l.Writer, a...)
|
||||
fmt.Fprint(l.Writer, a...) //nolint:errcheck // Interface ignores this error
|
||||
}
|
||||
|
||||
// Printf implements the gorm.Logger interface
|
||||
func (l LogWriter) Printf(format string, a ...any) {
|
||||
fmt.Fprintf(l.Writer, format, a...)
|
||||
fmt.Fprintf(l.Writer, format, a...) //nolint:errcheck // Interface ignores this error
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ func ParseBadgeLevels(m *irc.Message) BadgeCollection {
|
|||
badges := strings.Split(badgeString, ",")
|
||||
for _, b := range badges {
|
||||
badgeParts := strings.Split(b, "/")
|
||||
if len(badgeParts) != 2 { //nolint:gomnd // This is not a magic number but just an expected count
|
||||
if len(badgeParts) != 2 { //nolint:mnd // This is not a magic number but just an expected count
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ func (c *Client) BanUser(ctx context.Context, channel, username string, duration
|
|||
return errors.Wrap(err, "encoding payload")
|
||||
}
|
||||
|
||||
return errors.Wrap(
|
||||
return errors.Wrapf(
|
||||
c.Request(ctx, ClientRequestOpts{
|
||||
AuthType: AuthTypeBearerToken,
|
||||
Method: http.MethodPost,
|
||||
|
@ -89,7 +89,7 @@ func (c *Client) BanUser(ctx context.Context, channel, username string, duration
|
|||
return ValidateStatus(opts, resp)
|
||||
},
|
||||
}),
|
||||
"executing ban request",
|
||||
"executing ban request for %q in %q", username, channel,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ func (c *Client) SearchCategories(ctx context.Context, name string) ([]Category,
|
|||
|
||||
for {
|
||||
if err := c.Request(ctx, ClientRequestOpts{
|
||||
AuthType: AuthTypeBearerToken,
|
||||
AuthType: AuthTypeAppAccessToken,
|
||||
Method: http.MethodGet,
|
||||
OKStatus: http.StatusOK,
|
||||
Out: &resp,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
@ -27,12 +29,59 @@ type (
|
|||
TagIds []string `json:"tag_ids"` //revive:disable-line:var-naming // Disabled to prevent breaking change
|
||||
IsMature bool `json:"is_mature"`
|
||||
}
|
||||
|
||||
// StreamMarkerInfo contains information about a marker on a stream
|
||||
StreamMarkerInfo struct {
|
||||
ID string `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Description string `json:"description"`
|
||||
PositionSeconds int64 `json:"position_seconds"`
|
||||
}
|
||||
)
|
||||
|
||||
// ErrNoStreamsFound allows to differntiate between an HTTP error and
|
||||
// the fact there just is no stream found
|
||||
var ErrNoStreamsFound = errors.New("no streams found")
|
||||
|
||||
// CreateStreamMarker creates a marker for the currently running stream.
|
||||
// The stream must be live, no VoD, no upload and no re-run.
|
||||
// The description may be up to 140 chars and can be omitted.
|
||||
func (c *Client) CreateStreamMarker(ctx context.Context, description string) (marker StreamMarkerInfo, err error) {
|
||||
body := new(bytes.Buffer)
|
||||
|
||||
userID, _, err := c.GetAuthorizedUser(ctx)
|
||||
if err != nil {
|
||||
return marker, fmt.Errorf("getting ID for current user: %w", err)
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(body).Encode(struct {
|
||||
UserID string `json:"user_id"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}{
|
||||
UserID: userID,
|
||||
Description: description,
|
||||
}); err != nil {
|
||||
return marker, fmt.Errorf("encoding payload: %w", err)
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Data []StreamMarkerInfo `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.Request(ctx, ClientRequestOpts{
|
||||
AuthType: AuthTypeBearerToken,
|
||||
Body: body,
|
||||
Method: http.MethodPost,
|
||||
OKStatus: http.StatusOK,
|
||||
Out: &payload,
|
||||
URL: "https://api.twitch.tv/helix/streams/markers",
|
||||
}); err != nil {
|
||||
return marker, fmt.Errorf("creating marker: %w", err)
|
||||
}
|
||||
|
||||
return payload.Data[0], nil
|
||||
}
|
||||
|
||||
// GetCurrentStreamInfo returns the StreamInfo of the currently running
|
||||
// stream of the given username
|
||||
func (c *Client) GetCurrentStreamInfo(ctx context.Context, username string) (*StreamInfo, error) {
|
||||
|
|
150
pkg/twitch/videos.go
Normal file
150
pkg/twitch/videos.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
// GetVideoOpts contain the query parameter for the GetVideos query
|
||||
//
|
||||
// See https://dev.twitch.tv/docs/api/reference/#get-videos for details
|
||||
GetVideoOpts struct {
|
||||
ID string // Required: Exactly one of ID, UserID, GameID
|
||||
UserID string // Required: Exactly one of ID, UserID, GameID
|
||||
GameID string // Required: Exactly one of ID, UserID, GameID
|
||||
Language string // Optional: Use only with GameID
|
||||
Period GetVideoOptsPeriod // Optional: Use only with GameID or UserID
|
||||
Sort GetVideoOptsSort // Optional: Use only with GameID or UserID
|
||||
Type GetVideoOptsType // Optional: Use only with GameID or UserID
|
||||
First int64 // Optional: Use only with GameID or UserID
|
||||
After string // Optional: Use only with UserID
|
||||
Before string // Optional: Use only with UserID
|
||||
}
|
||||
|
||||
// GetVideoOptsPeriod represents a filter used to filter the list of
|
||||
// videos by when they were published
|
||||
GetVideoOptsPeriod string
|
||||
// GetVideoOptsSort represents the order to sort the returned videos in
|
||||
GetVideoOptsSort string
|
||||
// GetVideoOptsType represents a filter used to filter the list of
|
||||
// videos by the video's type
|
||||
GetVideoOptsType string
|
||||
|
||||
// Video contains information about a published video
|
||||
Video struct {
|
||||
ID string `json:"id"`
|
||||
StreamID *string `json:"stream_id"`
|
||||
UserID string `json:"user_id"`
|
||||
UserLogin string `json:"user_login"`
|
||||
UserName string `json:"user_name"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
URL string `json:"url"`
|
||||
ThumbnailURL string `json:"thumbnail_url"`
|
||||
Viewable string `json:"viewable"`
|
||||
ViewCount int64 `json:"view_count"`
|
||||
Language string `json:"language"`
|
||||
Type string `json:"type"`
|
||||
Duration string `json:"duration"`
|
||||
MutedSegments []struct {
|
||||
Duration int64 `json:"duration"`
|
||||
Offset int64 `json:"offset"`
|
||||
} `json:"muted_segments"`
|
||||
}
|
||||
)
|
||||
|
||||
// List of filters for GetVideoOpts.Period
|
||||
const (
|
||||
GetVideoOptsPeriodAll GetVideoOptsPeriod = "all"
|
||||
GetVideoOptsPeriodDay GetVideoOptsPeriod = "day"
|
||||
GetVideoOptsPeriodMonth GetVideoOptsPeriod = "month"
|
||||
GetVideoOptsPeriodWeek GetVideoOptsPeriod = "week"
|
||||
)
|
||||
|
||||
// List of sort options for GetVideoOpts.Sort
|
||||
const (
|
||||
GetVideoOptsSortTime GetVideoOptsSort = "time"
|
||||
GetVideoOptsSortTrending GetVideoOptsSort = "trending"
|
||||
GetVideoOptsSortViews GetVideoOptsSort = "views"
|
||||
)
|
||||
|
||||
// List of types for GetVideoOpts.Type
|
||||
const (
|
||||
GetVideoOptsTypeAll GetVideoOptsType = "all"
|
||||
GetVideoOptsTypeArchive GetVideoOptsType = "archive"
|
||||
GetVideoOptsTypeHighlight GetVideoOptsType = "highlight"
|
||||
GetVideoOptsTypeUpload GetVideoOptsType = "upload"
|
||||
)
|
||||
|
||||
// GetVideos fetches information about one or more published videos
|
||||
func (c *Client) GetVideos(ctx context.Context, opts GetVideoOpts) (videos []Video, err error) {
|
||||
optsCacheKey, err := opts.cacheKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting opts cache key: %w", err)
|
||||
}
|
||||
|
||||
cacheKey := []string{"currentVideos", optsCacheKey}
|
||||
if vids := c.apiCache.Get(cacheKey); vids != nil {
|
||||
return vids.([]Video), nil
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Data []Video `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.Request(ctx, ClientRequestOpts{
|
||||
AuthType: AuthTypeAppAccessToken,
|
||||
Method: http.MethodGet,
|
||||
OKStatus: http.StatusOK,
|
||||
Out: &payload,
|
||||
URL: fmt.Sprintf("https://api.twitch.tv/helix/videos?%s", opts.queryParams()),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("requesting videos: %w", err)
|
||||
}
|
||||
|
||||
// Videos can be changed at any moment, cache for a short period of time
|
||||
c.apiCache.Set(cacheKey, twitchMinCacheTime, payload.Data)
|
||||
|
||||
return payload.Data, nil
|
||||
}
|
||||
|
||||
func (g GetVideoOpts) cacheKey() (string, error) {
|
||||
h, err := hashstructure.Hash(g, hashstructure.FormatV2, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("hashing opts: %w", err)
|
||||
}
|
||||
|
||||
return strconv.FormatUint(h, 10), nil
|
||||
}
|
||||
|
||||
func (g GetVideoOpts) queryParams() string {
|
||||
params := url.Values{}
|
||||
|
||||
for k, v := range map[string]string{
|
||||
"id": g.ID,
|
||||
"user_id": g.UserID,
|
||||
"game_id": g.GameID,
|
||||
"language": g.Language,
|
||||
"period": string(g.Period),
|
||||
"sort": string(g.Sort),
|
||||
"type": string(g.Type),
|
||||
"first": strconv.FormatInt(g.First, 10),
|
||||
"after": g.After,
|
||||
"before": g.Before,
|
||||
} {
|
||||
if v != "" && v != "0" {
|
||||
params.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return params.Encode()
|
||||
}
|
|
@ -52,9 +52,7 @@ func (t *testTimerStore) InCooldown(tt TimerType, limiter, ruleID string) (bool,
|
|||
}
|
||||
|
||||
func (testTimerStore) getCooldownTimerKey(tt TimerType, limiter, ruleID string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%d:%s:%s", tt, limiter, ruleID)
|
||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(fmt.Sprintf("%d:%s:%s", tt, limiter, ruleID))))
|
||||
}
|
||||
|
||||
// Permit timer
|
||||
|
@ -69,7 +67,5 @@ func (t *testTimerStore) HasPermit(channel, username string) (bool, error) {
|
|||
}
|
||||
|
||||
func (testTimerStore) getPermitTimerKey(channel, username string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%d:%s:%s", TimerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@")))
|
||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(fmt.Sprintf("%d:%s:%s", TimerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@"))))))
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/Luzifer/twitch-bot/v3/internal/actors/linkdetector"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/linkprotect"
|
||||
logActor "github.com/Luzifer/twitch-bot/v3/internal/actors/log"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/marker"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/messagehook"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/modchannel"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/nuke"
|
||||
|
@ -78,6 +79,7 @@ var (
|
|||
linkdetector.Register,
|
||||
linkprotect.Register,
|
||||
logActor.Register,
|
||||
marker.Register,
|
||||
messagehook.Register,
|
||||
modchannel.Register,
|
||||
nuke.Register,
|
||||
|
|
|
@ -5,7 +5,7 @@ import "github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
|||
var (
|
||||
channelExtendedScopes = map[string]string{
|
||||
twitch.ScopeChannelEditCommercial: "run commercial",
|
||||
twitch.ScopeChannelManageBroadcast: "modify category / title",
|
||||
twitch.ScopeChannelManageBroadcast: "modify category / title, create markers",
|
||||
twitch.ScopeChannelManagePolls: "manage polls",
|
||||
twitch.ScopeChannelManagePredictions: "manage predictions",
|
||||
twitch.ScopeChannelManageRaids: "start raids",
|
||||
|
|
|
@ -1127,7 +1127,7 @@ export default {
|
|||
},
|
||||
|
||||
validateRaffleChannel() {
|
||||
if (!/^[a-zA-Z0-9]{4,25}$/.test(this.models.raffle.channel)) {
|
||||
if (!constants.REGEXP_USER.test(this.models.raffle.channel)) {
|
||||
return false
|
||||
}
|
||||
return null
|
||||
|
|
194
tools/go.mod
Normal file
194
tools/go.mod
Normal file
|
@ -0,0 +1,194 @@
|
|||
module tools
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/golangci/golangci-lint v1.61.0
|
||||
gotest.tools/gotestsum v1.12.0
|
||||
mvdan.cc/gofumpt v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
github.com/4meepo/tagalign v1.3.4 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.1 // indirect
|
||||
github.com/Antonboom/errname v0.1.13 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.9 // indirect
|
||||
github.com/Antonboom/testifylint v1.4.3 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.5.2 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bitfield/gotestdox v0.2.2 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.1 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.4.1 // indirect
|
||||
github.com/breml/bidichk v0.2.7 // indirect
|
||||
github.com/breml/errchkjson v0.3.6 // indirect
|
||||
github.com/butuzov/ireturn v0.3.0 // indirect
|
||||
github.com/butuzov/mirror v1.2.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.7.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.2.0 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/dnephin/pflag v1.0.7 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.6 // indirect
|
||||
github.com/go-critic/go-critic v0.11.4 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/modinfo v0.3.4 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.5.3 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
|
||||
github.com/jjti/go-spancheck v0.6.2 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
|
||||
github.com/kisielk/errcheck v1.7.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.10 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.1 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.4 // indirect
|
||||
github.com/ldez/tagliatelle v0.5.0 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgechev/revive v1.3.9 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.16.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.5 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.21.2 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/tenv v1.10.0 // indirect
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.12.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||
github.com/tetafro/godot v1.4.17 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ultraware/funlen v0.1.0 // indirect
|
||||
github.com/ultraware/whitespace v0.1.1 // indirect
|
||||
github.com/uudashr/gocognit v1.1.3 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.12.2 // indirect
|
||||
go-simpler.org/sloglint v0.7.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.5.1 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
|
||||
)
|
1002
tools/go.sum
Normal file
1002
tools/go.sum
Normal file
File diff suppressed because it is too large
Load diff
9
tools/tools.go
Normal file
9
tools/tools.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
//go:build tools
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "gotest.tools/gotestsum"
|
||||
_ "mvdan.cc/gofumpt"
|
||||
)
|
|
@ -234,7 +234,6 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration
|
|||
},
|
||||
{
|
||||
Topic: twitch.EventSubEventTypeChannelSuspiciousUserMessage,
|
||||
Version: twitch.EventSubTopicVersionBeta,
|
||||
Condition: twitch.EventSubCondition{BroadcasterUserID: userID, ModeratorUserID: userID},
|
||||
RequiredScopes: []string{twitch.ScopeModeratorReadSuspiciousUsers},
|
||||
Hook: t.handleEventSubSusUserMessage,
|
||||
|
@ -242,7 +241,6 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration
|
|||
},
|
||||
{
|
||||
Topic: twitch.EventSubEventTypeChannelSuspiciousUserUpdate,
|
||||
Version: twitch.EventSubTopicVersionBeta,
|
||||
Condition: twitch.EventSubCondition{BroadcasterUserID: userID, ModeratorUserID: userID},
|
||||
RequiredScopes: []string{twitch.ScopeModeratorReadSuspiciousUsers},
|
||||
Hook: t.handleEventSubSusUserUpdate,
|
||||
|
|
Loading…
Add table
Reference in a new issue