mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-09 16:50:01 +00:00
Compare commits
16 commits
795dece2e8
...
4186e16451
Author | SHA1 | Date | |
---|---|---|---|
4186e16451 | |||
1ac20921a1 | |||
4a15a7bf35 | |||
bcc5b4eba7 | |||
2bec4f82ed | |||
eac3b0ea34 | |||
e16c0367bb | |||
7d19bee9a4 | |||
220a501ab8 | |||
a5df68d921 | |||
fb57cb9304 | |||
0a53863b69 | |||
dc648d1dba | |||
a3a134fe36 | |||
db3c4f4efa | |||
29df9e59b5 |
86 changed files with 902 additions and 881 deletions
102
.github/workflows/test-and-build.yml
vendored
102
.github/workflows/test-and-build.yml
vendored
|
@ -85,4 +85,106 @@ jobs:
|
|||
draft: false
|
||||
generateReleaseNotes: false
|
||||
|
||||
database-integration:
|
||||
# Only execute db-server integration tests when sqlite based tests did run successfully
|
||||
needs: [test-and-build]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
container:
|
||||
image: luzifer/archlinux
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOPATH: /go
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
database: [mariadb, mysql, postgres]
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:11
|
||||
env:
|
||||
MYSQL_PASSWORD: twitch-bot-pass
|
||||
MYSQL_ROOT_PASSWORD: root-pass
|
||||
MYSQL_USER: twitch-bot
|
||||
|
||||
mysql:
|
||||
image: mysql:8
|
||||
env:
|
||||
MYSQL_PASSWORD: twitch-bot-pass
|
||||
MYSQL_ROOT_PASSWORD: root-pass
|
||||
MYSQL_USER: twitch-bot
|
||||
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_PASSWORD: twitch-bot-pass
|
||||
|
||||
steps:
|
||||
- name: Enable custom AUR package repo
|
||||
run: echo -e "[luzifer]\nSigLevel = Never\nServer = https://archrepo.hub.luzifer.io/\$arch" >>/etc/pacman.conf
|
||||
|
||||
- name: Install required packages
|
||||
run: |
|
||||
pacman -Syy --noconfirm \
|
||||
docker \
|
||||
git \
|
||||
go \
|
||||
make \
|
||||
mariadb-clients
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Marking workdir safe
|
||||
run: git config --global --add safe.directory /__w/twitch-bot/twitch-bot
|
||||
|
||||
# --- MySQL
|
||||
|
||||
- name: Set up MySQL service
|
||||
if: matrix.database == 'mysql'
|
||||
run: |
|
||||
mariadb -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
|
||||
|
||||
- name: Run tests against MySQL
|
||||
if: matrix.database == 'mysql'
|
||||
env:
|
||||
GO_TEST_DB_ENGINE: mysql
|
||||
GO_TEST_DB_DSN: twitch-bot:twitch-bot-pass@tcp(mysql:3306)/integration?charset=utf8mb4&parseTime=True
|
||||
run: make test
|
||||
|
||||
# --- MariaDB
|
||||
|
||||
- name: Set up MariaDB service
|
||||
if: matrix.database == 'mariadb'
|
||||
run: |
|
||||
mariadb -h mariadb -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
|
||||
|
||||
- name: Run tests against MariaDB
|
||||
if: matrix.database == 'mariadb'
|
||||
env:
|
||||
GO_TEST_DB_ENGINE: mysql
|
||||
GO_TEST_DB_DSN: twitch-bot:twitch-bot-pass@tcp(mariadb:3306)/integration?charset=utf8mb4&parseTime=True
|
||||
run: make test
|
||||
|
||||
# --- PostgreSQL
|
||||
|
||||
- name: Run tests against PostgreSQL
|
||||
if: matrix.database == 'postgres'
|
||||
env:
|
||||
GO_TEST_DB_ENGINE: postgres
|
||||
GO_TEST_DB_DSN: host=postgres user=postgres password=twitch-bot-pass dbname=postgres port=5432 sslmode=disable timezone=UTC
|
||||
run: make test
|
||||
|
||||
...
|
||||
|
|
24
History.md
24
History.md
|
@ -1,3 +1,27 @@
|
|||
# 3.18.0 / 2023-09-21
|
||||
|
||||
* New Features
|
||||
* [core] Add channel specific module configuration interface
|
||||
* [templating] Add `idForUsername` function
|
||||
* [templating] Add `usernameForID` function
|
||||
|
||||
* Improvements
|
||||
* [core] Add `user:manage:whispers` extended scope
|
||||
* [core] Update go-irc to v4.0.0
|
||||
|
||||
* Bugfixes
|
||||
* [ci] Update dependencies
|
||||
* [raffle] Insert newly created raffles with `NULL` reminder time
|
||||
|
||||
* Documentation
|
||||
* [docs] Add raffle documentation
|
||||
* [docs] Add raffle module as feature to start page
|
||||
* [docs] Fix broken preparations image
|
||||
|
||||
* Deprecations
|
||||
* [core] Mark twitch-token flag / envvar deprecated
|
||||
* [core] Remove v2 migration
|
||||
|
||||
# 3.17.0 / 2023-08-25
|
||||
|
||||
* New Features
|
||||
|
|
53
README.md
53
README.md
|
@ -38,8 +38,8 @@ Usage of twitch-bot:
|
|||
Supported sub-commands are:
|
||||
actor-docs Generate markdown documentation for available actors
|
||||
api-token <token-name> <scope> [...scope] Generate an api-token to be entered into the config
|
||||
migrate-v2 <old-file> Migrate old (*.json.gz) storage file into new database
|
||||
reset-secrets Remove encrypted data to reset encryption passphrase
|
||||
tpl-docs Generate markdown documentation for available template functions
|
||||
validate-config Try to load configuration file and report errors if any
|
||||
```
|
||||
|
||||
|
@ -93,54 +93,3 @@ Just pass the filename you want to use.
|
|||
--storage-conn-string 'storage.db' \
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Upgrade from `v2.x` to `v3.x`
|
||||
|
||||
With the release of `v3.0.0` the bot changed a lot introducing a new storage format. As that storage backend is not compatible with the `v2.x` storage you need to migrate it manually before starting a `v3.x` bot version the first time.
|
||||
|
||||
**Before starting the migration make sure to fully stop the bot!**
|
||||
|
||||
This section assumes you were starting your `v2.x` bot the following way:
|
||||
|
||||
```console
|
||||
# twitch-bot \
|
||||
--storage-file storage.json.gz
|
||||
--twitch-client <clientid> \
|
||||
--twitch-client-secret <secret>
|
||||
```
|
||||
|
||||
To execute the migration we need to provide the same `storage-encryption-pass` or `twitch-client` / `twitch-client-secret` combination if no `storage-encryption-pass` was used.
|
||||
|
||||
```console
|
||||
# twitch-bot \
|
||||
--storage-conn-type <database type> \
|
||||
--storage-conn-string <database connection string> \
|
||||
--twitch-client <clientid> \
|
||||
--twitch-client-secret <secret> \
|
||||
migrate-v2 storage.json.gz
|
||||
WARN[0000] No storage encryption passphrase was set, falling back to client-id:client-secret
|
||||
WARN[0000] Module registered unhandled query-param type module=status type=integer
|
||||
WARN[0000] Overlays dir not specified, no dir or non existent dir=
|
||||
INFO[0000] Starting migration... module=variables
|
||||
INFO[0000] Starting migration... module=mod_punish
|
||||
INFO[0000] Starting migration... module=mod_overlays
|
||||
INFO[0000] Starting migration... module=mod_quotedb
|
||||
INFO[0000] Starting migration... module=core
|
||||
INFO[0000] Starting migration... module=counter
|
||||
INFO[0000] Starting migration... module=permissions
|
||||
INFO[0000] Starting migration... module=timers
|
||||
INFO[0000] v2 storage file was migrated
|
||||
```
|
||||
|
||||
If you see the `v2 storage file was migrated` message the contents of your old storage file were migrated to the new database. The old file is not modified in this step.
|
||||
|
||||
Afterwards your need to adjust the start parameters of the bot:
|
||||
|
||||
```console
|
||||
# twitch-bot \
|
||||
--storage-conn-type <database type> \
|
||||
--storage-conn-string <database connection string> \
|
||||
--twitch-client <clientid> \
|
||||
--twitch-client-secret <secret> \
|
||||
```
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -3,9 +3,9 @@ package main
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
)
|
||||
|
@ -31,10 +31,10 @@ func newTwitchUserStateStore() *twitchUserStateStore {
|
|||
|
||||
func parseTwitchUserState(m *irc.Message) (*twitchUserState, error) {
|
||||
var (
|
||||
color, _ = m.GetTag("color")
|
||||
displayName, _ = m.GetTag("display-name")
|
||||
color, _ = m.Tags["color"]
|
||||
displayName, _ = m.Tags["display-name"]
|
||||
emoteSets []string
|
||||
rawSets, _ = m.GetTag("emote-sets")
|
||||
rawSets, _ = m.Tags["emote-sets"]
|
||||
)
|
||||
|
||||
if rawSets != "" {
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/v2migrator"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.Add(cliRegistryEntry{
|
||||
Name: "migrate-v2",
|
||||
Description: "Migrate old (*.json.gz) storage file into new database",
|
||||
Params: []string{"<old-file>"},
|
||||
Run: func(args []string) error {
|
||||
if len(args) < 2 { //nolint:gomnd // Just a count of parameters
|
||||
return errors.New("Usage: twitch-bot migrate-v2 <old storage file>")
|
||||
}
|
||||
|
||||
v2s := v2migrator.NewStorageFile()
|
||||
if err := v2s.Load(args[1], cfg.StorageEncryptionPass); err != nil {
|
||||
return errors.Wrap(err, "loading v2 storage file")
|
||||
}
|
||||
|
||||
if err := v2s.Migrate(db); err != nil {
|
||||
return errors.Wrap(err, "migrating v2 storage file")
|
||||
}
|
||||
|
||||
log.Info("v2 storage file was migrated")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -11,12 +11,12 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/gofrs/uuid/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/irc.v4"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
|
@ -56,6 +56,7 @@ type (
|
|||
PermitAllowModerator bool `yaml:"permit_allow_moderator"`
|
||||
PermitTimeout time.Duration `yaml:"permit_timeout"`
|
||||
RawLog string `yaml:"raw_log"`
|
||||
ModuleConfig plugins.ModuleConfig `yaml:"module_config"`
|
||||
Rules []*plugins.Rule `yaml:"rules"`
|
||||
Variables map[string]interface{} `yaml:"variables"`
|
||||
|
||||
|
|
|
@ -10,10 +10,12 @@ You are tired of all those cloud-bots working only sometimes, messing up at rand
|
|||
|
||||
**Open-Source:** This means you (or any developer you trust) can look up how things work inside the bot, can modify the bot yourself and be sure the bot does not use the access you are granting it to do stuff you don't want it to do. Also you are not dependent on some company to keep the bot running for you but are in control over it. In case I'm no longer willing to develop the bot, it will not cease to exist but can be developed further by anyone.
|
||||
|
||||
**Overlays:** The bot contains a web-server to host custom overlays which can be built like any website with **HTML and Javascript**. Some default overlays are included ready to use and for everything not available in the default distribution there is a helper library available to connect to the bot and work with events and bot state.
|
||||
[**Overlays:**]({{< ref "overlays/_index.md" >}}) The bot contains a web-server to host custom overlays which can be built like any website with **HTML and Javascript**. Some default overlays are included ready to use and for everything not available in the default distribution there is a helper library available to connect to the bot and work with events and bot state.
|
||||
|
||||
**YAML Configuration:** The whole configuration is stored in a single YAML file, not in any proprietary format. You can simply create a backup of that file and even if some mistake or broken server happens you simply put back the configuration file and all of your rules, auto-messages, API-keys are instantly back again. (What's not in the configuration file is the data the bot stores like counters, events and variables.)
|
||||
|
||||
**Common Database Formats:** All the data mentioned in the last point is stored in a common database format like **SQLite**, **MySQL** or **PostgreSQL**. With exception of the credentials all the data is stored in a plain format which means you can use well-known database tooling to create backups. This also means you can use custom tooling to do with the data what you want! (Though a warning for this point: The database schema is not guaranteed to be stable! While it's possible I do not recommend directly accessing the data in the database for other tools.)
|
||||
|
||||
**API-First Design:** The bot is built to have an API and an included documentation for this API. Most of its functionality is exposed through the API and you can easily build tooling against that API to make to bot do your bidding.
|
||||
|
||||
[**Raffle-Module:**]({{< ref "modules/raffle.md" >}}) You don't need any other tool to do giveaways, the bot contains a raffle management including entrant restrictions, random picks, luck modifiers, automated text posts and of course entrant management.
|
||||
|
|
|
@ -77,6 +77,17 @@ auto_messages:
|
|||
# Disable message using templating, must yield string `true` to disable the automated message
|
||||
disable_on_template: '{{ ne .myvariable true }}'
|
||||
|
||||
# Module configuration by channel or defining bot-wide defaults. See
|
||||
# module specific documentation for options to configure in this
|
||||
# section. All modules come with internal defaults so there is no
|
||||
# need to configure this but you can overwrite the internal defaults.
|
||||
module_config:
|
||||
some-module: # Name of the module to configure
|
||||
default: # Bot-wide, fallback for all channels
|
||||
some_option: true
|
||||
mychannel: # Channel-specific, only valid for this channel
|
||||
some_option: false
|
||||
|
||||
# List of rules. See documentation for details or use web-interface
|
||||
# to configure.
|
||||
rules: # See below for examples
|
||||
|
|
|
@ -232,6 +232,19 @@ Example:
|
|||
< test - oops
|
||||
```
|
||||
|
||||
### `idForUsername`
|
||||
|
||||
Returns the user-id for the given username
|
||||
|
||||
Syntax: `idForUsername <username>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ idForUsername "twitch" }}
|
||||
* 12826
|
||||
```
|
||||
|
||||
### `inList`
|
||||
|
||||
Tests whether a string is in a given list of strings (for conditional templates).
|
||||
|
@ -376,7 +389,7 @@ Example:
|
|||
|
||||
```
|
||||
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||
< Your int this hour: 84%
|
||||
< Your int this hour: 46%
|
||||
```
|
||||
|
||||
### `streamUptime`
|
||||
|
@ -446,6 +459,19 @@ Example:
|
|||
* Weather for Hamburg, DE: Few clouds with a temperature of 22 C (71.6 F). [...]
|
||||
```
|
||||
|
||||
### `usernameForID`
|
||||
|
||||
Returns the current login name of an user-id
|
||||
|
||||
Syntax: `usernameForID <user-id>`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# {{ usernameForID "12826" }}
|
||||
* twitch
|
||||
```
|
||||
|
||||
### `variable`
|
||||
|
||||
Returns the variable value or default in case it is empty
|
||||
|
@ -458,12 +484,3 @@ Example:
|
|||
# {{ variable "foo" "fallback" }} - {{ variable "unsetvar" "fallback" }}
|
||||
* test - fallback
|
||||
```
|
||||
|
||||
## Upgrade from `v2.x` to `v3.x`
|
||||
|
||||
When adding [sprig](https://masterminds.github.io/sprig/) function collection some functions collided and needed replacement. You need to adapt your templates accordingly:
|
||||
|
||||
- Math functions (`add`, `div`, `mod`, `mul`, `multiply`, `sub`) were replaced with their sprig-equivalent and are now working with integers instead of floats. If you need them to continue to work with floats you need to use their [float-variants](https://masterminds.github.io/sprig/mathf.html).
|
||||
- `now` does no longer format the current date as a string but return the current date. You need to replace this: `now "2006-01-02"` becomes `now | date "2006-01-02"`.
|
||||
- `concat` is now used to concat arrays. To join strings you will need to modify your code: `concat ":" "string1" "string2"` becomes `lists "string1" "string2" | join ":"`.
|
||||
- `toLower` / `toUpper` need to be replaced with their sprig equivalent `lower` and `upper`.
|
||||
|
|
|
@ -15,5 +15,5 @@ Registering your application is a relatively straight-forward process:
|
|||
|
||||
- Go to https://dev.twitch.tv/console/apps/create
|
||||
- Fill out the form you are presented with. You can choose any **Name** you want for your bot. I'd recommend using one you later will recognize your bot under. For the **OAuth Redirect URL** choose the URL you want to have the bot available under later. If you want to have it running locally you can choose `http://localhost:3000/` for this field.
|
||||
![](/screen-twitch-console-register-app.png)
|
||||
![]({{< static "screen-twitch-console-register-app.png" >}})
|
||||
- After registering your application go into the application you've just created and click the **New Secret** button. Note down the **Client-Id** and **Client-Secret** in a safe place. You will need them in the [Configuration]({{< ref "configuration.md" >}}) step.
|
||||
|
|
10
docs/content/modules/_index.md
Normal file
10
docs/content/modules/_index.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Modules
|
||||
---
|
||||
|
||||
{{< lead >}}
|
||||
Aside of the core functionality of being a bot in a Twitch channel the bot contains additional modules to make channel management easier.
|
||||
{{< /lead >}}
|
||||
|
||||
- The bot can serve all of your [**Overlays**]({{< ref "../overlays/_index.md" >}}) for you providing you with sound-alerts, alerts for various events and everything you can imagine yourself using Custom Events
|
||||
- With the [**Raffle**]({{< ref "raffle.md" >}}) module you can create giveaways with various settings
|
66
docs/content/modules/raffle.md
Normal file
66
docs/content/modules/raffle.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: Raffle
|
||||
---
|
||||
|
||||
{{< lead >}}
|
||||
Using the raffle module you can create giveaways with various settings, timers and pick one or multiple winners. You just have to send the good yourself…
|
||||
{{< /lead >}}
|
||||
|
||||
## General Overview
|
||||
|
||||
![]({{< static "raffle-overview.png" >}})
|
||||
|
||||
In the overview you can see a list of your raffles and their status. You can edit, start / stop, copy, delete them or access the list of entrants from here.
|
||||
|
||||
The screenshot above shows one draft of a raffle together with one currently active.
|
||||
|
||||
![]({{< static "raffle-entrants.png" >}})
|
||||
|
||||
You can access the entrants list through the "group of people" button in the raffle overview. This becomes available as soon as the raffle has started.
|
||||
|
||||
In this list you can see the status, the nickname and the time of entry for each entrant. The status will be a person (<i class="fas fa-user"></i>) for someone joined through the **Everyone** allowance, a heart (<i class="fas fa-heart"></i>) for a follower, a star (<i class="fas fa-star"></i>) for a subscriber and a diamond (<i class="fas fa-gem"></i>) for a VIP. The list will update itself when there are changes in the entree-list.
|
||||
|
||||
![]({{< static "raffle-entrants-closed.png" >}})
|
||||
|
||||
After the raffle has been closed (either through the timer or by clicking the button) a winner can be picked through the "Pick Winner" button. A winner will display a crown before their status, the first chat message after being picked below the name and a "recycle" button to re-draw them. If you choose to re-draw a winner the crown will get striked and greyed out. A re-drawn winner can not be picked again (re-rdrawing without any candidates will cause an error and void the slot)!
|
||||
|
||||
### Recommendations
|
||||
|
||||
- As you can see below there are many options to configure and you probably will use the same texts (and maybe even some other options too) in every raffle. That's why you can see a `TEMPLATE` raffle in the screenshot above. That's a fully configured and never started raffle I keep around. When starting a new raffle I will just use the "copy" button to create a copy of that raffle, edit the copy, adjust the title and maybe options I don't like and can start the raffle saving a lot of work in the process especially creating the texts. You can have as many templates as you like if you're doing different raffles over and over.
|
||||
|
||||
## Raffle Configuration
|
||||
|
||||
### General Settings
|
||||
|
||||
![]({{< static "raffle-general-config.png" >}})
|
||||
|
||||
Within the general settings you will configure how your raffle behaves once started:
|
||||
|
||||
- **Channel** configures where it will take place: Straight forward, put your channel without the leading `#`.
|
||||
- **Keyword** is what users must type in order to participate. In general they are used to type commands like `!enter` or `!key` for Steam-Key giveaways so I'd advice to stick to a command format. You should ensure not to use the same command in two raffles active at the same time though it is possible: if two raffles are using the same keyword, the user writing the keyword once will enter **both** raffles.
|
||||
- **Title** should reflect what you're giving away and is available in the texts (more below). So for example this could be `Steam-Key: Starfield`.
|
||||
- **Allowed Entries** configure who can take part in your giveaway. Pay attention these conditions are **or**-connected, so the chatter must only have one condition matching and not all of them!
|
||||
- **Everyone** is straight forward: No conditions are imposed.
|
||||
- **Followers, since `X` min** means all followers can participate if they are followed at least `X` minutes ago.
|
||||
- **Subscribers** is straight forward again: If they have a subscriber-badge, they can join.
|
||||
- **VIPs** is the same just they do need a VIP badge.
|
||||
- **Luck Modifiers** are kinda tricky as they configure the size of each ticket and therefore manipulate the probability to be chosen. As stated in the text below the base modifier for **Everyone** is `1.0` and you can modifiy the "luck" for all others. Pay attention with this: if you for example disable the VIPs checkbox in **Allowed Entries** the VIPs luck modifier will **not** be used! That VIP will then enter as a subscriber or follower or even as "everyone" and get the respective modifier.
|
||||
- **Times** configure when and for how long the raffle will take place:
|
||||
- **Auto-Start** can be configured and if it is, the raffle will open itself at that point of time (within 1 minute). If you don't set this you need to start the raffle yourself.
|
||||
- **Duration** configures how long the raffle will run. This is only relevant if **Close At** is unset. (Internally on starting the raffle **Close At** will be set to `now + duration` if **Close At** is empty.)
|
||||
- **Close At** marks the end of the raffle. The raffle will automatically get closed at that point of time (within 1 minute).
|
||||
- **Respond in** adds a time window where the bot will record the first message of the picked user after they have been picked. You will see that message in the entrants list: Useful for channels with a lot going on in the chat so you don't miss their response. After this time window is over no response will be recorded.
|
||||
|
||||
### Texts
|
||||
|
||||
![]({{< static "raffle-texts.png" >}})
|
||||
|
||||
The texts do support templating and do have the same format like other templates i.e. in rules. You can enable or disable each of them though I'd recommend to keep all of them enabled (maybe except the "failed entry" message).
|
||||
|
||||
- **Message on successful entry** will be posted as soon as the chatter is added to the entrants list.
|
||||
- **Message on failed entry** will be posted in case the chatter is not entered (could be they are already entered or the bot encountered any other error while adding them).
|
||||
- **Message on winner draw** will be posted for the chatter getting picked when drawing the winner: if you disable this you still can tell them they won when picking them.
|
||||
- **Periodic reminder every `X` min** is a message to remember chatters (and tell new ones) there is a raffle open. It will be posted every `X` minutes, first time when opening the raffle.
|
||||
- **Message on raffle close** will be posted when the raffle closes (either you closed it manually or the **Close At** time is reached).
|
||||
|
||||
Within the templates you do have access to the variables `.user` and `.raffle` (which represents the raffle object). Have a look at the default templates for examples what you can do with them.
|
5
docs/layouts/shortcodes/static.html
Normal file
5
docs/layouts/shortcodes/static.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{- .Scratch.Set "path" (.Get 0) -}}
|
||||
{{- if hasPrefix (.Scratch.Get "path") "/" -}}
|
||||
{{- .Scratch.Set "path" (slicestr (.Scratch.Get "path") 1) -}}
|
||||
{{- end -}}
|
||||
{{- .Scratch.Get "path" | absLangURL -}}
|
3
docs/static/raffle-entrants-closed.png
vendored
Normal file
3
docs/static/raffle-entrants-closed.png
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a188c2de21f24c3db3ba56d123e1f76fb6920456d5e6f388af7eeecf3e1d85df
|
||||
size 14721
|
3
docs/static/raffle-entrants.png
vendored
Normal file
3
docs/static/raffle-entrants.png
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4ef7d42174f56cdb00e028b3f23b4ab9e027fd9572d44bd066b4692e7940c1e0
|
||||
size 12866
|
3
docs/static/raffle-general-config.png
vendored
Normal file
3
docs/static/raffle-general-config.png
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7ed4fbfafd57ae86821ca2ed8ea6a383e0b4453dc3ee7942010e23f886f5846c
|
||||
size 78876
|
3
docs/static/raffle-overview.png
vendored
Normal file
3
docs/static/raffle-overview.png
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f47ddf30ae4211d7a0a323182b1331521a09915f73f5a0bcc5b7c6f01ca7310
|
||||
size 16172
|
3
docs/static/raffle-texts.png
vendored
Normal file
3
docs/static/raffle-texts.png
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c484cda001007c2817c4e0f989c2614bc11213f361b07c9b7ad57fb15c27c07d
|
||||
size 68284
|
|
@ -8,8 +8,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/go-irc/irc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
korvike "github.com/Luzifer/korvike/functions"
|
||||
|
|
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -95,10 +95,7 @@ func init() {
|
|||
)
|
||||
|
||||
tplFuncs.Register("tag", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
||||
return func(tag string) string {
|
||||
s, _ := m.GetTag(tag)
|
||||
return s
|
||||
}
|
||||
return func(tag string) string { return m.Tags[tag] }
|
||||
}, plugins.TemplateFuncDocumentation{
|
||||
Description: "Takes the message sent to the channel, returns the value of the tag specified",
|
||||
Syntax: "tag <tagname>",
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tplFuncs.Register("displayName", plugins.GenericTemplateFunctionGetter(tplTwitchDisplayName), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the display name the specified user set for themselves",
|
||||
Syntax: "displayName <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}`,
|
||||
FakedOutput: "Luziferus - foobar",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("doesFollowLongerThan", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollowLongerThan), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns whether `from` follows `to` for more than `duration`",
|
||||
Syntax: "doesFollowLongerThan <from> <to> <duration>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ doesFollowLongerThan "tezrian" "luziferus" "168h" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("doesFollow", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollow), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns whether `from` follows `to`",
|
||||
Syntax: "doesFollow <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ doesFollow "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("followAge", plugins.GenericTemplateFunctionGetter(tplTwitchFollowAge), plugins.TemplateFuncDocumentation{
|
||||
Description: "Looks up when `from` followed `to` and returns the duration between then and now",
|
||||
Syntax: "followAge <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ followAge "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "15004h14m59.116620989s",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("followDate", plugins.GenericTemplateFunctionGetter(tplTwitchFollowDate), plugins.TemplateFuncDocumentation{
|
||||
Description: "Looks up when `from` followed `to`",
|
||||
Syntax: "followDate <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ followDate "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "2021-04-10 16:07:07 +0000 UTC",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("lastPoll", plugins.GenericTemplateFunctionGetter(tplTwitchLastPoll), plugins.TemplateFuncDocumentation{
|
||||
Description: "Gets the last (currently running or archived) poll for the given channel (the channel must have given extended permission for poll access!)",
|
||||
Syntax: "lastPoll <channel>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `Last Poll: {{ (lastPoll .channel).Title }}`,
|
||||
FakedOutput: "Last Poll: Und wie siehts im Template aus?",
|
||||
},
|
||||
Remarks: "See schema of returned object in [`pkg/twitch/polls.go#L13`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/polls.go#L13)",
|
||||
})
|
||||
|
||||
tplFuncs.Register("profileImage", plugins.GenericTemplateFunctionGetter(tplTwitchProfileImage), plugins.TemplateFuncDocumentation{
|
||||
Description: "Gets the URL of the given users profile image",
|
||||
Syntax: "profileImage <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ profileImage .username }}`,
|
||||
FakedOutput: "https://static-cdn.jtvnw.net/jtv_user_pictures/[...].png",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("recentGame", plugins.GenericTemplateFunctionGetter(tplTwitchRecentGame), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the last played game name of the specified user (see shoutout example) or the `fallback` if the game could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||
Syntax: "recentGame <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||
FakedOutput: "Metro Exodus - none",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("recentTitle", plugins.GenericTemplateFunctionGetter(tplTwitchRecentTitle), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the last stream title of the specified user or the `fallback` if the title could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||
Syntax: "recentTitle <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||
FakedOutput: "Die Oper haben wir überlebt, mal sehen was uns sonst noch alles töten möchte… - none",
|
||||
},
|
||||
})
|
||||
|
||||
tplFuncs.Register("streamUptime", plugins.GenericTemplateFunctionGetter(tplTwitchStreamUptime), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the duration the stream is online (causes an error if no current stream is found)",
|
||||
Syntax: "streamUptime <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}`,
|
||||
FakedOutput: "3 hours, 56 minutes",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchDisplayName(username string, v ...string) (string, error) {
|
||||
displayName, err := twitchClient.GetDisplayNameForUser(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || displayName == "") {
|
||||
return v[0], nil //nolint:nilerr // Default value, no need to return error
|
||||
}
|
||||
|
||||
return displayName, err
|
||||
}
|
||||
|
||||
func tplTwitchDoesFollow(from, to string) (bool, error) {
|
||||
_, err := twitchClient.GetFollowDate(from, to)
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
|
||||
case errors.Is(err, twitch.ErrUserDoesNotFollow):
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
return false, errors.Wrap(err, "getting follow date")
|
||||
}
|
||||
}
|
||||
|
||||
func tplTwitchFollowAge(from, to string) (time.Duration, error) {
|
||||
since, err := twitchClient.GetFollowDate(from, to)
|
||||
return time.Since(since), errors.Wrap(err, "getting follow date")
|
||||
}
|
||||
|
||||
func tplTwitchFollowDate(from, to string) (time.Time, error) {
|
||||
return twitchClient.GetFollowDate(from, to)
|
||||
}
|
||||
|
||||
func tplTwitchDoesFollowLongerThan(from, to string, t any) (bool, error) {
|
||||
var (
|
||||
age time.Duration
|
||||
err error
|
||||
)
|
||||
|
||||
switch v := t.(type) {
|
||||
case int64:
|
||||
age = time.Duration(v) * time.Second
|
||||
|
||||
case string:
|
||||
if age, err = time.ParseDuration(v); err != nil {
|
||||
return false, errors.Wrap(err, "parsing duration")
|
||||
}
|
||||
|
||||
default:
|
||||
return false, errors.Errorf("unexpected input for duration %t", t)
|
||||
}
|
||||
|
||||
fd, err := twitchClient.GetFollowDate(from, to)
|
||||
switch {
|
||||
case err == nil:
|
||||
return time.Since(fd) > age, nil
|
||||
|
||||
case errors.Is(err, twitch.ErrUserDoesNotFollow):
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
return false, errors.Wrap(err, "getting follow date")
|
||||
}
|
||||
}
|
||||
|
||||
func tplTwitchLastPoll(username string) (*twitch.PollInfo, error) {
|
||||
hasPollAccess, err := accessService.HasAnyPermissionForChannel(username, twitch.ScopeChannelReadPolls, twitch.ScopeChannelManagePolls)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "checking read-poll-permission")
|
||||
}
|
||||
|
||||
if !hasPollAccess {
|
||||
return nil, errors.Errorf("not authorized to read polls for channel %s", username)
|
||||
}
|
||||
|
||||
tc, err := accessService.GetTwitchClientForChannel(strings.TrimLeft(username, "#"), access.ClientConfig{
|
||||
TwitchClient: cfg.TwitchClient,
|
||||
TwitchClientSecret: cfg.TwitchClientSecret,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting twitch client for user")
|
||||
}
|
||||
|
||||
poll, err := tc.GetLatestPoll(context.Background(), strings.TrimLeft(username, "#"))
|
||||
return poll, errors.Wrap(err, "getting last poll")
|
||||
}
|
||||
|
||||
func tplTwitchProfileImage(username string) (string, error) {
|
||||
user, err := twitchClient.GetUserInformation(strings.TrimLeft(username, "#@"))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getting user info")
|
||||
}
|
||||
|
||||
return user.ProfileImageURL, nil
|
||||
}
|
||||
|
||||
func tplTwitchRecentGame(username string, v ...string) (string, error) {
|
||||
game, _, err := twitchClient.GetRecentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || game == "") {
|
||||
return v[0], nil
|
||||
}
|
||||
|
||||
return game, err
|
||||
}
|
||||
|
||||
func tplTwitchRecentTitle(username string, v ...string) (string, error) {
|
||||
_, title, err := twitchClient.GetRecentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || title == "") {
|
||||
return v[0], nil
|
||||
}
|
||||
|
||||
return title, err
|
||||
}
|
||||
|
||||
func tplTwitchStreamUptime(username string) (time.Duration, error) {
|
||||
si, err := twitchClient.GetCurrentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Since(si.StartedAt), nil
|
||||
}
|
37
go.mod
37
go.mod
|
@ -1,17 +1,16 @@
|
|||
module github.com/Luzifer/twitch-bot/v3
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/Luzifer/go-openssl/v4 v4.1.0
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.0
|
||||
github.com/Luzifer/go_helpers/v2 v2.20.0
|
||||
github.com/Luzifer/korvike/functions v0.11.0
|
||||
github.com/Luzifer/rconfig/v2 v2.4.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/getsentry/sentry-go v0.22.0
|
||||
github.com/getsentry/sentry-go v0.23.0
|
||||
github.com/glebarez/sqlite v1.9.0
|
||||
github.com/go-git/go-git/v5 v5.8.0
|
||||
github.com/go-irc/irc v2.1.0+incompatible
|
||||
github.com/go-git/go-git/v5 v5.8.1
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/gofrs/uuid/v3 v3.1.2
|
||||
|
@ -25,18 +24,20 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
gopkg.in/irc.v4 v4.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
gorm.io/gorm v1.25.4
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.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 v0.0.0-20230518184743-7afd39499903 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
|
@ -49,7 +50,7 @@ require (
|
|||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // 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
|
||||
|
@ -58,7 +59,7 @@ require (
|
|||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/vault/api v1.9.2 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
|
@ -66,7 +67,7 @@ require (
|
|||
github.com/itchyny/timefmt-go v0.1.5 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.2 // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // 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,21 +84,21 @@ require (
|
|||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.1.1 // indirect
|
||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
gopkg.in/validator.v2 v2.0.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.24.0 // indirect
|
||||
modernc.org/memory v1.7.1 // indirect
|
||||
modernc.org/sqlite v1.25.0 // indirect
|
||||
)
|
||||
|
|
102
go.sum
102
go.sum
|
@ -1,7 +1,9 @@
|
|||
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=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Luzifer/go-openssl/v4 v4.1.0 h1:8qi3Z6f8Aflwub/Cs4FVSmKUEg/lC8GlODbR2TyZ+nM=
|
||||
github.com/Luzifer/go-openssl/v4 v4.1.0/go.mod h1:3i1T3Pe6eQK19d86WhuQzjLyMwBaNmGmt3ZceWpWVa4=
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.0 h1:39/cZnBMg+/YC3hn1eVI02KyJfNwhiRO3AlxwZAvu0c=
|
||||
github.com/Luzifer/go-openssl/v4 v4.2.0/go.mod h1:CZZZWY0buCtkxrkqDPQYigC4Kn55UuO97TEoV+hwz2s=
|
||||
github.com/Luzifer/go_helpers/v2 v2.20.0 h1:OyCUs7TFGwfJpGqD21KEKKOXy92jetw2l7dlmG7HZnA=
|
||||
github.com/Luzifer/go_helpers/v2 v2.20.0/go.mod h1:KPGjImwm51SmOTZMd9XUsT241gHYJuEyLrS/omQ4/Dw=
|
||||
github.com/Luzifer/korvike/functions v0.11.0 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q=
|
||||
|
@ -18,20 +20,21 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
|
|||
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 v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
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.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
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.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -41,6 +44,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
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-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/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=
|
||||
|
@ -49,24 +53,27 @@ github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
|||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM=
|
||||
github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE=
|
||||
github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
|
||||
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
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.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||
github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ=
|
||||
github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
|
||||
github.com/go-irc/irc v2.1.0+incompatible h1:pg7pMVq5OYQbqTxceByD/EN8VIsba7DtKn49rsCnG8Y=
|
||||
github.com/go-irc/irc v2.1.0+incompatible/go.mod h1:jJILTRy8s/qOvusiKifAEfhQMVwft1ZwQaVJnnzmyX4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
||||
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
|
@ -75,6 +82,7 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
|
|||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
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/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=
|
||||
|
@ -89,10 +97,12 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
|||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/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/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
|
@ -125,8 +135,9 @@ github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3
|
|||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||
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 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.4 h1:NJY/hSAoWy0EhQQdDxxoBlwyJex/xC2qNWXD0up6D48=
|
||||
github.com/hashicorp/go-sockaddr v1.0.4/go.mod h1:LPGW7TbF+cTE2o/bBlBWD4XG8rgRJeIurURxH5kEHr8=
|
||||
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=
|
||||
|
@ -154,8 +165,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg=
|
||||
github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
|
||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
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=
|
||||
|
@ -167,6 +178,7 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
|
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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=
|
||||
|
@ -177,6 +189,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
|||
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=
|
||||
|
@ -202,14 +215,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||
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=
|
||||
|
@ -222,6 +239,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
|||
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
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=
|
||||
|
@ -237,21 +255,24 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
|||
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.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
|
||||
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
||||
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
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=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb h1:G0Rrif8QdbAz7Xy53H4Xumy6TuyKHom8pu8z/jdLwwM=
|
||||
|
@ -261,13 +282,13 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
|||
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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
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=
|
||||
|
@ -288,8 +309,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
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.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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=
|
||||
|
@ -299,6 +320,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
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=
|
||||
|
@ -314,7 +336,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-20211007075335-d3039528d8ac/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=
|
||||
|
@ -323,14 +344,15 @@ 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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
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=
|
||||
|
@ -339,9 +361,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -352,8 +375,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
|||
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.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
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=
|
||||
|
@ -368,8 +391,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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=
|
||||
|
@ -386,15 +412,15 @@ gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5
|
|||
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
|
||||
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
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/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
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.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/memory v1.7.1 h1:9J+2/GKTlV503mk3yv8QJ6oEpRCUrRy0ad8TXEPoV8M=
|
||||
modernc.org/memory v1.7.1/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
||||
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
|
|
|
@ -3,8 +3,8 @@ package announce
|
|||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/linkdetector"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package deleteactor
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -29,7 +29,7 @@ func Register(args plugins.RegistrationArguments) error {
|
|||
type actor struct{}
|
||||
|
||||
func (a actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, _ *plugins.FieldCollection) (preventCooldown bool, err error) {
|
||||
msgID, ok := m.Tags.GetTag("id")
|
||||
msgID, ok := m.Tags["id"]
|
||||
if !ok || msgID == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package eventmod
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package linkdetector
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/linkcheck"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/clipdetector"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
|
@ -172,7 +172,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
}
|
||||
|
||||
case "delete":
|
||||
msgID, ok := m.Tags.GetTag("id")
|
||||
msgID, ok := m.Tags["id"]
|
||||
if !ok || msgID == "" {
|
||||
return false, errors.New("found no mesage id")
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
|
@ -210,7 +210,7 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
}
|
||||
|
||||
enforcement := strings.NewReplacer(
|
||||
"$msgid", string(stMsg.Msg.Tags["id"]),
|
||||
"$msgid", stMsg.Msg.Tags["id"],
|
||||
"$user", plugins.DeriveUser(stMsg.Msg, nil),
|
||||
).Replace(actionName)
|
||||
|
||||
|
@ -218,7 +218,7 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
continue
|
||||
}
|
||||
|
||||
if err = action(channel, rawMatch, string(stMsg.Msg.Tags["id"]), plugins.DeriveUser(stMsg.Msg, nil)); err != nil {
|
||||
if err = action(channel, rawMatch, stMsg.Msg.Tags["id"], plugins.DeriveUser(stMsg.Msg, nil)); err != nil {
|
||||
return false, errors.Wrap(err, "executing action")
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
|
@ -16,7 +16,6 @@ import (
|
|||
const (
|
||||
actorNamePunish = "punish"
|
||||
actorNameResetPunish = "reset-punish"
|
||||
moduleUUID = "44ab4646-ce50-4e16-9353-c1f0eb68962b"
|
||||
|
||||
oneWeek = 168 * time.Hour
|
||||
)
|
||||
|
@ -173,7 +172,7 @@ func (a actorPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eve
|
|||
}
|
||||
|
||||
case "delete":
|
||||
msgID, ok := m.Tags.GetTag("id")
|
||||
msgID, ok := m.Tags["id"]
|
||||
if !ok || msgID == "" {
|
||||
return false, errors.New("found no mesage id")
|
||||
}
|
||||
|
|
|
@ -3,16 +3,15 @@ package quotedb
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
const (
|
||||
actorName = "quotedb"
|
||||
moduleUUID = "917c83ee-ed40-41e4-a558-1c2e59fdf1f5"
|
||||
actorName = "quotedb"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package raw
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
@ -124,12 +124,12 @@ func (a actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData
|
|||
}
|
||||
|
||||
if attrs.MustBool("as_reply", ptrBoolFalse) {
|
||||
id, ok := m.GetTag("id")
|
||||
id, ok := m.Tags["id"]
|
||||
if ok {
|
||||
if ircMessage.Tags == nil {
|
||||
ircMessage.Tags = make(irc.Tags)
|
||||
}
|
||||
ircMessage.Tags["reply-parent-msg-id"] = irc.TagValue(id)
|
||||
ircMessage.Tags["reply-parent-msg-id"] = id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package shield
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -3,8 +3,8 @@ package shoutout
|
|||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package stopexec
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package whisper
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
|
|
@ -3,8 +3,8 @@ package customevent
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -56,7 +56,7 @@ type (
|
|||
TextEntryFailPost bool `json:"textEntryFailPost"`
|
||||
TextReminder string `json:"textReminder"`
|
||||
TextReminderInterval time.Duration `json:"textReminderInterval"`
|
||||
TextReminderNextSend time.Time `json:"-"`
|
||||
TextReminderNextSend *time.Time `json:"-"`
|
||||
TextReminderPost bool `json:"textReminderPost"`
|
||||
TextWin string `json:"textWin"`
|
||||
TextWinPost bool `json:"textWinPost"`
|
||||
|
@ -143,7 +143,7 @@ func (d *dbClient) AutoSendReminders() (err error) {
|
|||
var rr []raffle
|
||||
|
||||
if err = d.db.DB().
|
||||
Where("status = ? AND text_reminder_post = ? AND text_reminder_next_send < ?", raffleStatusActive, true, time.Now().UTC()).
|
||||
Where("status = ? AND text_reminder_post = ? AND (text_reminder_next_send IS NULL OR text_reminder_next_send < ?)", raffleStatusActive, true, time.Now().UTC()).
|
||||
Find(&rr).
|
||||
Error; err != nil {
|
||||
return errors.Wrap(err, "fetching raffles to send reminders")
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -84,9 +84,9 @@ func handleRaffleEntry(m *irc.Message, channel, user string) error {
|
|||
|
||||
re := raffleEntry{
|
||||
RaffleID: r.ID,
|
||||
UserID: string(m.Tags["user-id"]),
|
||||
UserID: m.Tags["user-id"],
|
||||
UserLogin: user,
|
||||
UserDisplayName: string(m.Tags["display-name"]),
|
||||
UserDisplayName: m.Tags["display-name"],
|
||||
EnteredAt: time.Now().UTC(),
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ func handleRaffleEntry(m *irc.Message, channel, user string) error {
|
|||
}
|
||||
|
||||
raffleEventFields := plugins.FieldCollectionFromData(map[string]any{
|
||||
"user_id": string(m.Tags["user-id"]),
|
||||
"user_id": m.Tags["user-id"],
|
||||
"user": user,
|
||||
})
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ type (
|
|||
TwitchClient string
|
||||
TwitchClientSecret string
|
||||
|
||||
FallbackToken string
|
||||
FallbackToken string // DEPRECATED
|
||||
|
||||
TokenUpdateHook func()
|
||||
}
|
||||
|
@ -228,20 +228,6 @@ func (s Service) RemoveExendedTwitchCredentials(channel string) error {
|
|||
)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetBotUsername and SetExtendedTwitchCredentials
|
||||
// instead. This function is only required for the v2 migration tool.
|
||||
func (s Service) SetBotTwitchCredentials(accessToken, refreshToken string) (err error) {
|
||||
if err = s.db.StoreEncryptedCoreMeta(coreMetaKeyBotToken, accessToken); err != nil {
|
||||
return errors.Wrap(err, "storing bot access token")
|
||||
}
|
||||
|
||||
if err = s.db.StoreEncryptedCoreMeta(coreMetaKeyBotRefreshToken, refreshToken); err != nil {
|
||||
return errors.Wrap(err, "storing bot refresh token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Service) SetBotUsername(channel string) (err error) {
|
||||
return errors.Wrap(
|
||||
s.db.StoreCoreMeta(coreMetaKeyBotUsername, strings.TrimLeft(channel, "#")),
|
||||
|
|
110
internal/template/twitch/follow.go
Normal file
110
internal/template/twitch/follow.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
regFn = append(
|
||||
regFn,
|
||||
tplTwitchDoesFollow,
|
||||
tplTwitchDoesFollowLongerThan,
|
||||
tplTwitchFollowAge,
|
||||
tplTwitchFollowDate,
|
||||
)
|
||||
}
|
||||
|
||||
func tplTwitchDoesFollowLongerThan(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("doesFollowLongerThan", plugins.GenericTemplateFunctionGetter(func(from, to string, t any) (bool, error) {
|
||||
var (
|
||||
age time.Duration
|
||||
err error
|
||||
)
|
||||
|
||||
switch v := t.(type) {
|
||||
case int64:
|
||||
age = time.Duration(v) * time.Second
|
||||
|
||||
case string:
|
||||
if age, err = time.ParseDuration(v); err != nil {
|
||||
return false, errors.Wrap(err, "parsing duration")
|
||||
}
|
||||
|
||||
default:
|
||||
return false, errors.Errorf("unexpected input for duration %t", t)
|
||||
}
|
||||
|
||||
fd, err := args.GetTwitchClient().GetFollowDate(from, to)
|
||||
switch {
|
||||
case err == nil:
|
||||
return time.Since(fd) > age, nil
|
||||
|
||||
case errors.Is(err, twitch.ErrUserDoesNotFollow):
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
return false, errors.Wrap(err, "getting follow date")
|
||||
}
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns whether `from` follows `to` for more than `duration`",
|
||||
Syntax: "doesFollowLongerThan <from> <to> <duration>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ doesFollowLongerThan "tezrian" "luziferus" "168h" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchDoesFollow(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("doesFollow", plugins.GenericTemplateFunctionGetter(func(from, to string) (bool, error) {
|
||||
_, err := args.GetTwitchClient().GetFollowDate(from, to)
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
|
||||
case errors.Is(err, twitch.ErrUserDoesNotFollow):
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
return false, errors.Wrap(err, "getting follow date")
|
||||
}
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns whether `from` follows `to`",
|
||||
Syntax: "doesFollow <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ doesFollow "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchFollowAge(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("followAge", plugins.GenericTemplateFunctionGetter(func(from, to string) (time.Duration, error) {
|
||||
since, err := args.GetTwitchClient().GetFollowDate(from, to)
|
||||
return time.Since(since), errors.Wrap(err, "getting follow date")
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Looks up when `from` followed `to` and returns the duration between then and now",
|
||||
Syntax: "followAge <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ followAge "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "15004h14m59.116620989s",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchFollowDate(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("followDate", plugins.GenericTemplateFunctionGetter(func(from, to string) (time.Time, error) {
|
||||
return args.GetTwitchClient().GetFollowDate(from, to)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Looks up when `from` followed `to`",
|
||||
Syntax: "followDate <from> <to>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ followDate "tezrian" "luziferus" }}`,
|
||||
FakedOutput: "2021-04-10 16:07:07 +0000 UTC",
|
||||
},
|
||||
})
|
||||
}
|
46
internal/template/twitch/poll.go
Normal file
46
internal/template/twitch/poll.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
regFn = append(
|
||||
regFn,
|
||||
tplTwitchLastPoll,
|
||||
)
|
||||
}
|
||||
|
||||
func tplTwitchLastPoll(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("lastPoll", plugins.GenericTemplateFunctionGetter(func(username string) (*twitch.PollInfo, error) {
|
||||
hasPollAccess, err := args.HasAnyPermissionForChannel(username, twitch.ScopeChannelReadPolls, twitch.ScopeChannelManagePolls)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "checking read-poll-permission")
|
||||
}
|
||||
|
||||
if !hasPollAccess {
|
||||
return nil, errors.Errorf("not authorized to read polls for channel %s", username)
|
||||
}
|
||||
|
||||
tc, err := args.GetTwitchClientForChannel(strings.TrimLeft(username, "#"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting twitch client for user")
|
||||
}
|
||||
|
||||
poll, err := tc.GetLatestPoll(context.Background(), strings.TrimLeft(username, "#"))
|
||||
return poll, errors.Wrap(err, "getting last poll")
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Gets the last (currently running or archived) poll for the given channel (the channel must have given extended permission for poll access!)",
|
||||
Syntax: "lastPoll <channel>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `Last Poll: {{ (lastPoll .channel).Title }}`,
|
||||
FakedOutput: "Last Poll: Und wie siehts im Template aus?",
|
||||
},
|
||||
Remarks: "See schema of returned object in [`pkg/twitch/polls.go#L13`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/polls.go#L13)",
|
||||
})
|
||||
}
|
70
internal/template/twitch/stream.go
Normal file
70
internal/template/twitch/stream.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
regFn = append(
|
||||
regFn,
|
||||
tplTwitchRecentGame,
|
||||
tplTwitchRecentTitle,
|
||||
tplTwitchStreamUptime,
|
||||
)
|
||||
}
|
||||
|
||||
func tplTwitchRecentGame(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("recentGame", plugins.GenericTemplateFunctionGetter(func(username string, v ...string) (string, error) {
|
||||
game, _, err := args.GetTwitchClient().GetRecentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || game == "") {
|
||||
return v[0], nil
|
||||
}
|
||||
|
||||
return game, err
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the last played game name of the specified user (see shoutout example) or the `fallback` if the game could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||
Syntax: "recentGame <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||
FakedOutput: "Metro Exodus - none",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchRecentTitle(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("recentTitle", plugins.GenericTemplateFunctionGetter(func(username string, v ...string) (string, error) {
|
||||
_, title, err := args.GetTwitchClient().GetRecentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || title == "") {
|
||||
return v[0], nil
|
||||
}
|
||||
|
||||
return title, err
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the last stream title of the specified user or the `fallback` if the title could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||
Syntax: "recentTitle <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||
FakedOutput: "Die Oper haben wir überlebt, mal sehen was uns sonst noch alles töten möchte… - none",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchStreamUptime(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("streamUptime", plugins.GenericTemplateFunctionGetter(func(username string) (time.Duration, error) {
|
||||
si, err := args.GetTwitchClient().GetCurrentStreamInfo(strings.TrimLeft(username, "#"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Since(si.StartedAt), nil
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the duration the stream is online (causes an error if no current stream is found)",
|
||||
Syntax: "streamUptime <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}`,
|
||||
FakedOutput: "3 hours, 56 minutes",
|
||||
},
|
||||
})
|
||||
}
|
17
internal/template/twitch/twitch.go
Normal file
17
internal/template/twitch/twitch.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Package twitch defines Twitch related template functions not having
|
||||
// their place in any other package
|
||||
package twitch
|
||||
|
||||
import (
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
var regFn []func(plugins.RegistrationArguments)
|
||||
|
||||
func Register(args plugins.RegistrationArguments) error {
|
||||
for _, fn := range regFn {
|
||||
fn(args)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
internal/template/twitch/user.go
Normal file
81
internal/template/twitch/user.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
regFn = append(
|
||||
regFn,
|
||||
tplTwitchDisplayName,
|
||||
tplTwitchIDForUsername,
|
||||
tplTwitchProfileImage,
|
||||
tplTwitchUsernameForID,
|
||||
)
|
||||
}
|
||||
|
||||
func tplTwitchDisplayName(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("displayName", plugins.GenericTemplateFunctionGetter(func(username string, v ...string) (string, error) {
|
||||
displayName, err := args.GetTwitchClient().GetDisplayNameForUser(strings.TrimLeft(username, "#"))
|
||||
if len(v) > 0 && (err != nil || displayName == "") {
|
||||
return v[0], nil //nolint:nilerr // Default value, no need to return error
|
||||
}
|
||||
|
||||
return displayName, err
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the display name the specified user set for themselves",
|
||||
Syntax: "displayName <username> [fallback]",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}`,
|
||||
FakedOutput: "Luziferus - foobar",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchIDForUsername(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("idForUsername", plugins.GenericTemplateFunctionGetter(func(username string) (string, error) {
|
||||
return args.GetTwitchClient().GetIDForUsername(username)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the user-id for the given username",
|
||||
Syntax: "idForUsername <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ idForUsername "twitch" }}`,
|
||||
FakedOutput: "12826",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchProfileImage(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("profileImage", plugins.GenericTemplateFunctionGetter(func(username string) (string, error) {
|
||||
user, err := args.GetTwitchClient().GetUserInformation(strings.TrimLeft(username, "#@"))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getting user info")
|
||||
}
|
||||
|
||||
return user.ProfileImageURL, nil
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Gets the URL of the given users profile image",
|
||||
Syntax: "profileImage <username>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ profileImage .username }}`,
|
||||
FakedOutput: "https://static-cdn.jtvnw.net/jtv_user_pictures/[...].png",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tplTwitchUsernameForID(args plugins.RegistrationArguments) {
|
||||
args.RegisterTemplateFunction("usernameForID", plugins.GenericTemplateFunctionGetter(func(id string) (string, error) {
|
||||
return args.GetTwitchClient().GetUsernameForID(context.Background(), id)
|
||||
}), plugins.TemplateFuncDocumentation{
|
||||
Description: "Returns the current login name of an user-id",
|
||||
Syntax: "usernameForID <user-id>",
|
||||
Example: &plugins.TemplateFuncDocumentationExample{
|
||||
Template: `{{ usernameForID "12826" }}`,
|
||||
FakedOutput: "twitch",
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package v2migrator
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/counter"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/variables"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/service/timer"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
)
|
||||
|
||||
func (s storageFile) migrateCoreKV(db database.Connector) (err error) {
|
||||
as, err := access.New(db)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating access service")
|
||||
}
|
||||
|
||||
//nolint:staticcheck // Use of deprecated function is fine for this purpose
|
||||
if err = as.SetBotTwitchCredentials(s.BotAccessToken, s.BotRefreshToken); err != nil {
|
||||
return errors.Wrap(err, "setting bot credentials")
|
||||
}
|
||||
|
||||
if err = db.StoreEncryptedCoreMeta("event_sub_secret", s.EventSubSecret); err != nil {
|
||||
return errors.Wrap(err, "storing bot eventsub token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s storageFile) migrateCounters(db database.Connector) (err error) {
|
||||
for counterName, value := range s.Counters {
|
||||
if err = counter.UpdateCounter(db, counterName, value, true); err != nil {
|
||||
return errors.Wrap(err, "storing counter value")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s storageFile) migratePermissions(db database.Connector) (err error) {
|
||||
as, err := access.New(db)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating access service")
|
||||
}
|
||||
|
||||
for channel, perms := range s.ExtendedPermissions {
|
||||
if err = as.SetExtendedTwitchCredentials(
|
||||
channel,
|
||||
perms.AccessToken,
|
||||
perms.RefreshToken,
|
||||
perms.Scopes,
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "storing channel %q credentials", channel)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s storageFile) migrateTimers(db database.Connector) (err error) {
|
||||
ts, err := timer.New(db, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating timer service")
|
||||
}
|
||||
|
||||
for id, expiry := range s.Timers {
|
||||
if err := ts.SetTimer(id, expiry.Time); err != nil {
|
||||
return errors.Wrap(err, "storing counter in database")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s storageFile) migrateVariables(db database.Connector) (err error) {
|
||||
for key, value := range s.Variables {
|
||||
if err := variables.SetVariable(db, key, value); err != nil {
|
||||
return errors.Wrap(err, "updating value in database")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Luzifer/go-openssl/v4"
|
||||
)
|
||||
|
||||
const encryptedValuePrefix = "enc:"
|
||||
|
||||
type encryptAction uint8
|
||||
|
||||
const (
|
||||
handleTagsDecrypt encryptAction = iota
|
||||
handleTagsEncrypt
|
||||
)
|
||||
|
||||
var osslClient = openssl.New()
|
||||
|
||||
// DecryptFields iterates through the given struct and decrypts all
|
||||
// fields marked with a struct tag of `encrypt:"true"`. The fields
|
||||
// are directly manipulated and the value is replaced.
|
||||
//
|
||||
// The input object needs to be a pointer to a struct!
|
||||
func DecryptFields(obj interface{}, passphrase string) error {
|
||||
return handleEncryptedTags(obj, passphrase, handleTagsDecrypt)
|
||||
}
|
||||
|
||||
// EncryptFields iterates through the given struct and encrypts all
|
||||
// fields marked with a struct tag of `encrypt:"true"`. The fields
|
||||
// are directly manipulated and the value is replaced.
|
||||
//
|
||||
// The input object needs to be a pointer to a struct!
|
||||
func EncryptFields(obj interface{}, passphrase string) error {
|
||||
return handleEncryptedTags(obj, passphrase, handleTagsEncrypt)
|
||||
}
|
||||
|
||||
//nolint:gocognit,gocyclo // Reflect loop, cannot reduce complexity
|
||||
func handleEncryptedTags(obj interface{}, passphrase string, action encryptAction) error {
|
||||
// Check we got a pointer and can manipulate the struct
|
||||
if kind := reflect.TypeOf(obj).Kind(); kind != reflect.Ptr {
|
||||
return errors.Errorf("expected pointer to struct, got %s", kind)
|
||||
}
|
||||
|
||||
// Check we got a struct in the pointer
|
||||
if kind := reflect.ValueOf(obj).Elem().Kind(); kind != reflect.Struct {
|
||||
return errors.Errorf("expected pointer to struct, got pointer to %s", kind)
|
||||
}
|
||||
|
||||
// Iterate over fields to find encrypted fields to manipulate
|
||||
st := reflect.ValueOf(obj).Elem()
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
v := st.Field(i)
|
||||
t := st.Type().Field(i)
|
||||
|
||||
if t.PkgPath != "" && !t.Anonymous {
|
||||
// Caught us an non-exported field, ignore that one
|
||||
continue
|
||||
}
|
||||
|
||||
hasEncryption := t.Tag.Get("encrypt") == "true"
|
||||
|
||||
switch t.Type.Kind() {
|
||||
// Type: Map - see whether value is struct
|
||||
case reflect.Map:
|
||||
if t.Type.Elem().Kind() == reflect.Ptr && t.Type.Elem().Elem().Kind() == reflect.Struct {
|
||||
for _, k := range v.MapKeys() {
|
||||
if err := handleEncryptedTags(v.MapIndex(k).Interface(), passphrase, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Type: Pointer - Recurse if not nil and struct inside
|
||||
case reflect.Ptr:
|
||||
if !v.IsNil() && v.Elem().Kind() == reflect.Struct && t.Type != reflect.TypeOf(&time.Time{}) {
|
||||
if err := handleEncryptedTags(v.Interface(), passphrase, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Type: String - Replace value if required
|
||||
case reflect.String:
|
||||
if hasEncryption {
|
||||
newValue, err := manipulateValue(v.String(), passphrase, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "manipulating value")
|
||||
}
|
||||
v.SetString(newValue)
|
||||
}
|
||||
|
||||
// Type: Struct - Welcome to recursion
|
||||
case reflect.Struct:
|
||||
if t.Type != reflect.TypeOf(time.Time{}) {
|
||||
if err := handleEncryptedTags(v.Addr().Interface(), passphrase, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We don't support anything else. Yet.
|
||||
default:
|
||||
if hasEncryption {
|
||||
return errors.Errorf("unsupported field type for encyption: %s", t.Type.Kind())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func manipulateValue(val, passphrase string, action encryptAction) (string, error) {
|
||||
switch action {
|
||||
case handleTagsDecrypt:
|
||||
if !strings.HasPrefix(val, encryptedValuePrefix) {
|
||||
// This is not an encrypted string: Return the value itself for
|
||||
// working with legacy values in storage
|
||||
return val, nil
|
||||
}
|
||||
|
||||
d, err := osslClient.DecryptBytes(passphrase, []byte(strings.TrimPrefix(val, encryptedValuePrefix)), openssl.PBKDF2SHA256)
|
||||
return string(d), errors.Wrap(err, "decrypting value")
|
||||
|
||||
case handleTagsEncrypt:
|
||||
if strings.HasPrefix(val, encryptedValuePrefix) {
|
||||
// This is an encrypted string: shouldn't happen but whatever
|
||||
return val, nil
|
||||
}
|
||||
|
||||
e, err := osslClient.EncryptBytes(passphrase, []byte(val), openssl.PBKDF2SHA256)
|
||||
return encryptedValuePrefix + string(e), errors.Wrap(err, "encrypting value")
|
||||
|
||||
default:
|
||||
return "", errors.New("invalid action")
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package v2migrator
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/apimodules/overlays"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
)
|
||||
|
||||
type (
|
||||
storageModOverlays struct {
|
||||
ChannelEvents map[string][]overlays.SocketMessage `json:"channel_events"`
|
||||
}
|
||||
)
|
||||
|
||||
func (s storageModOverlays) migrate(db database.Connector) (err error) {
|
||||
for channel, evts := range s.ChannelEvents {
|
||||
for _, evt := range evts {
|
||||
if err := overlays.AddChannelEvent(db, channel, evt); err != nil {
|
||||
return errors.Wrap(err, "storing event to database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package v2migrator
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/quotedb"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
)
|
||||
|
||||
type (
|
||||
storageModQuoteDB struct {
|
||||
ChannelQuotes map[string][]string `json:"channel_quotes"`
|
||||
}
|
||||
)
|
||||
|
||||
func (s storageModQuoteDB) migrate(db database.Connector) (err error) {
|
||||
for channel, quotes := range s.ChannelQuotes {
|
||||
if err := quotedb.SetQuotes(db, channel, quotes); err != nil {
|
||||
return errors.Wrap(err, "setting quotes for channel")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package v2migrator
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/v2migrator/crypt"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
||||
type (
|
||||
Migrator interface {
|
||||
Load(filename, encryptionPass string) error
|
||||
Migrate(db database.Connector) error
|
||||
}
|
||||
|
||||
storageExtendedPermission struct {
|
||||
AccessToken string `encrypt:"true" json:"access_token,omitempty"`
|
||||
RefreshToken string `encrypt:"true" json:"refresh_token,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
storageFile struct {
|
||||
Counters map[string]int64 `json:"counters"`
|
||||
Timers map[string]plugins.TimerEntry `json:"timers"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
|
||||
ModuleStorage struct {
|
||||
ModOverlays storageModOverlays `json:"f9ca2b3a-baf6-45ea-a347-c626168665e8"`
|
||||
ModQuoteDB storageModQuoteDB `json:"917c83ee-ed40-41e4-a558-1c2e59fdf1f5"`
|
||||
} `json:"module_storage"`
|
||||
|
||||
ExtendedPermissions map[string]*storageExtendedPermission `json:"extended_permissions"`
|
||||
|
||||
EventSubSecret string `encrypt:"true" json:"event_sub_secret,omitempty"`
|
||||
|
||||
BotAccessToken string `encrypt:"true" json:"bot_access_token,omitempty"`
|
||||
BotRefreshToken string `encrypt:"true" json:"bot_refresh_token,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewStorageFile() Migrator {
|
||||
return &storageFile{
|
||||
Counters: map[string]int64{},
|
||||
Timers: map[string]plugins.TimerEntry{},
|
||||
Variables: map[string]string{},
|
||||
|
||||
ExtendedPermissions: map[string]*storageExtendedPermission{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storageFile) Load(filename, encryptionPass string) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Store init state
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "open storage file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
zf, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create gzip reader")
|
||||
}
|
||||
defer zf.Close()
|
||||
|
||||
if err = json.NewDecoder(zf).Decode(s); err != nil {
|
||||
return errors.Wrap(err, "decode storage object")
|
||||
}
|
||||
|
||||
if err = crypt.DecryptFields(s, encryptionPass); err != nil {
|
||||
return errors.Wrap(err, "decrypting storage object")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s storageFile) Migrate(db database.Connector) error {
|
||||
var bat string
|
||||
err := db.ReadCoreMeta("bot_access_token", &bat)
|
||||
switch {
|
||||
case err == nil:
|
||||
return errors.New("Access token is set, database already initialized")
|
||||
|
||||
case errors.Is(err, database.ErrCoreMetaNotFound):
|
||||
// This is the expected state
|
||||
|
||||
default:
|
||||
return errors.Wrap(err, "checking for bot access token")
|
||||
}
|
||||
|
||||
for name, fn := range map[string]func(database.Connector) error{
|
||||
// Core
|
||||
"core": s.migrateCoreKV,
|
||||
"counter": s.migrateCounters,
|
||||
"permissions": s.migratePermissions,
|
||||
"timers": s.migrateTimers,
|
||||
"variables": s.migrateVariables,
|
||||
// Modules
|
||||
"mod_overlays": s.ModuleStorage.ModOverlays.migrate,
|
||||
"mod_quotedb": s.ModuleStorage.ModQuoteDB.migrate,
|
||||
} {
|
||||
logrus.WithField("module", name).Info("Starting migration...")
|
||||
if err = fn(db); err != nil {
|
||||
return errors.Wrapf(err, "executing %q migration", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
26
irc.go
26
irc.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
|
@ -9,9 +10,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -45,9 +46,11 @@ func registerRawMessageHandler(fn plugins.RawMessageHandlerFunc) error {
|
|||
}
|
||||
|
||||
type ircHandler struct {
|
||||
conn *tls.Conn
|
||||
c *irc.Client
|
||||
user string
|
||||
c *irc.Client
|
||||
conn *tls.Conn
|
||||
ctx context.Context
|
||||
ctxCancelFn func()
|
||||
user string
|
||||
}
|
||||
|
||||
func newIRCHandler() (*ircHandler, error) {
|
||||
|
@ -58,6 +61,8 @@ func newIRCHandler() (*ircHandler, error) {
|
|||
return nil, errors.Wrap(err, "fetching username")
|
||||
}
|
||||
|
||||
h.ctx, h.ctxCancelFn = context.WithCancel(context.Background())
|
||||
|
||||
conn, err := tls.Dial("tcp", "irc.chat.twitch.tv:6697", nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "connect to IRC server")
|
||||
|
@ -86,7 +91,10 @@ func newIRCHandler() (*ircHandler, error) {
|
|||
|
||||
func (i ircHandler) Client() *irc.Client { return i.c }
|
||||
|
||||
func (i ircHandler) Close() error { return i.conn.Close() }
|
||||
func (i ircHandler) Close() error {
|
||||
i.ctxCancelFn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i ircHandler) ExecuteJoins(channels []string) {
|
||||
for _, ch := range channels {
|
||||
|
@ -197,7 +205,7 @@ func (i ircHandler) Handle(c *irc.Client, m *irc.Message) {
|
|||
}
|
||||
}
|
||||
|
||||
func (i ircHandler) Run() error { return errors.Wrap(i.c.Run(), "running IRC client") }
|
||||
func (i ircHandler) Run() error { return errors.Wrap(i.c.RunContext(i.ctx), "running IRC client") }
|
||||
|
||||
func (i ircHandler) SendMessage(m *irc.Message) error { return i.c.WriteMessage(m) }
|
||||
|
||||
|
@ -209,8 +217,8 @@ func (ircHandler) getChannel(m *irc.Message) string {
|
|||
}
|
||||
|
||||
func (i ircHandler) handleClearChat(m *irc.Message) {
|
||||
seconds, secondsErr := strconv.Atoi(string(m.Tags["ban-duration"]))
|
||||
targetUserID, hasTargetUserID := m.Tags.GetTag("target-user-id")
|
||||
seconds, secondsErr := strconv.Atoi(m.Tags["ban-duration"])
|
||||
targetUserID, hasTargetUserID := m.Tags["target-user-id"]
|
||||
|
||||
var (
|
||||
evt *string
|
||||
|
@ -493,7 +501,7 @@ func (i ircHandler) handleTwitchWhisper(m *irc.Message) {
|
|||
}
|
||||
|
||||
func (ircHandler) tagToNumeric(m *irc.Message, tag string, fallback int64) int64 {
|
||||
tv := string(m.Tags[tag])
|
||||
tv := m.Tags[tag]
|
||||
if tv == "" {
|
||||
return fallback
|
||||
}
|
||||
|
|
6
main.go
6
main.go
|
@ -51,7 +51,7 @@ var (
|
|||
StorageEncryptionPass string `flag:"storage-encryption-pass" default:"" description:"Passphrase to encrypt secrets inside storage (defaults to twitch-client:twitch-client-secret)"`
|
||||
TwitchClient string `flag:"twitch-client" default:"" description:"Client ID to act as"`
|
||||
TwitchClientSecret string `flag:"twitch-client-secret" default:"" description:"Secret for the Client ID"`
|
||||
TwitchToken string `flag:"twitch-token" default:"" description:"OAuth token valid for client (fallback if no token was set in interface)"`
|
||||
TwitchToken string `flag:"twitch-token" default:"" description:"OAuth token valid for client (fallback if no token was set in interface) -- DEPRECATED"`
|
||||
ValidateConfig bool `flag:"validate-config,v" default:"false" description:"Loads the config, logs any errors and quits with status 0 on success"`
|
||||
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||
WaitForSelfcheck time.Duration `flag:"wait-for-selfcheck" default:"60s" description:"Maximum time to wait for the self-check to respond when behind load-balancers"`
|
||||
|
@ -113,6 +113,10 @@ func initApp() error {
|
|||
}, ":")
|
||||
}
|
||||
|
||||
if cfg.TwitchToken != "" {
|
||||
log.Warn("You are using the DEPRECATED --twitch-token flag / TWITCH_TOKEN env variable, please switch to web-based auth! - This flag will be removed in a later release!")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
17
main_test.go
17
main_test.go
|
@ -13,9 +13,22 @@ import (
|
|||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
var (
|
||||
dbEngine = "sqlite"
|
||||
dbDSN = "file::memory:?cache=shared"
|
||||
|
||||
if db, err = database.New("sqlite", "file::memory:?cache=shared", "encpass"); err != nil {
|
||||
err error
|
||||
)
|
||||
|
||||
if v := os.Getenv("GO_TEST_DB_ENGINE"); v != "" {
|
||||
dbEngine = v
|
||||
}
|
||||
|
||||
if v := os.Getenv("GO_TEST_DB_DSN"); v != "" {
|
||||
dbDSN = v
|
||||
}
|
||||
|
||||
if db, err = database.New(dbEngine, dbDSN, "go-test-static-encryption"); err != nil {
|
||||
log.WithError(err).Fatal("opening storage backend")
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"gopkg.in/irc.v4"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -32,7 +32,7 @@ func ParseBadgeLevels(m *irc.Message) BadgeCollection {
|
|||
return out
|
||||
}
|
||||
|
||||
badgeString, ok := m.GetTag("badges")
|
||||
badgeString, ok := m.Tags["badges"]
|
||||
if !ok || len(badgeString) == 0 {
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ const (
|
|||
ScopeModeratorReadFollowers = "moderator:read:followers"
|
||||
ScopeModeratorReadShoutouts = "moderator:read:shoutouts"
|
||||
ScopeUserManageChatColor = "user:manage:chat_color"
|
||||
ScopeUserManageWhispers = "user:manage:whispers"
|
||||
|
||||
// Deprecated v5 scope but used in chat
|
||||
ScopeV5ChannelEditor = "channel_editor"
|
||||
|
|
|
@ -159,6 +159,39 @@ func (c *Client) GetIDForUsername(username string) (string, error) {
|
|||
return payload.Data[0].ID, nil
|
||||
}
|
||||
|
||||
// GetUsernameForID retrieves the login name (not the display name)
|
||||
// for the given user ID
|
||||
func (c *Client) GetUsernameForID(ctx context.Context, id string) (string, error) {
|
||||
cacheKey := []string{"usernameForID", id}
|
||||
if d := c.apiCache.Get(cacheKey); d != nil {
|
||||
return d.(string), nil
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Data []User `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.Request(ClientRequestOpts{
|
||||
AuthType: AuthTypeAppAccessToken,
|
||||
Context: ctx,
|
||||
Method: http.MethodGet,
|
||||
OKStatus: http.StatusOK,
|
||||
Out: &payload,
|
||||
URL: fmt.Sprintf("https://api.twitch.tv/helix/users?id=%s", id),
|
||||
}); err != nil {
|
||||
return "", errors.Wrap(err, "request channel info")
|
||||
}
|
||||
|
||||
if l := len(payload.Data); l != 1 {
|
||||
return "", errors.Errorf("unexpected number of users returned: %d", l)
|
||||
}
|
||||
|
||||
// The username for an ID will not change (often), cache for a long time
|
||||
c.apiCache.Set(cacheKey, timeDay, payload.Data[0].Login)
|
||||
|
||||
return payload.Data[0].Login, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetUserInformation(user string) (*User, error) {
|
||||
user = strings.TrimLeft(user, "#@")
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"gopkg.in/irc.v4"
|
||||
)
|
||||
|
||||
func DeriveChannel(m *irc.Message, evtData *FieldCollection) string {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
|
@ -43,6 +43,8 @@ type (
|
|||
|
||||
LoggerCreationFunc func(moduleName string) *log.Entry
|
||||
|
||||
ModuleConfigGetterFunc func(module, channel string) *FieldCollection
|
||||
|
||||
MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *FieldCollection) (string, error)
|
||||
|
||||
MsgModificationFunc func(*irc.Message) error
|
||||
|
@ -65,6 +67,8 @@ type (
|
|||
GetDatabaseConnector func() database.Connector
|
||||
// GetLogger returns a sirupsen log.Entry pre-configured with the module name
|
||||
GetLogger LoggerCreationFunc
|
||||
// GetModuleConfigForChannel returns the module configuration for the given channel if available
|
||||
GetModuleConfigForChannel ModuleConfigGetterFunc
|
||||
// GetTwitchClient retrieves a fully configured Twitch client with initialized cache
|
||||
GetTwitchClient func() *twitch.Client
|
||||
// GetTwitchClientForChannel retrieves a fully configured Twitch client with initialized cache for extended permission channels
|
||||
|
|
37
plugins/moduleConfig.go
Normal file
37
plugins/moduleConfig.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package plugins
|
||||
|
||||
import "strings"
|
||||
|
||||
// DefaultConfigName is the name the default configuration must have
|
||||
// when defined
|
||||
const DefaultConfigName = "default"
|
||||
|
||||
type (
|
||||
// ModuleConfig represents a mapping of configurations per channel
|
||||
// and module
|
||||
ModuleConfig map[string]map[string]*FieldCollection
|
||||
)
|
||||
|
||||
// GetChannelConfig reads the channel specific configuration for the
|
||||
// given module. This is created by taking an empty FieldCollection,
|
||||
// merging in the default configuration and finally overwriting all
|
||||
// existing channel configurations.
|
||||
func (m ModuleConfig) GetChannelConfig(module, channel string) *FieldCollection {
|
||||
channel = strings.TrimLeft(channel, "#@")
|
||||
composed := NewFieldCollection()
|
||||
|
||||
for _, i := range []string{DefaultConfigName, channel} {
|
||||
f := m[module][i]
|
||||
if f == nil {
|
||||
// That config does not exist, don't apply
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range f.Data() {
|
||||
// Overwrite all keys defined in this config
|
||||
composed.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return composed
|
||||
}
|
38
plugins/moduleConfig_test.go
Normal file
38
plugins/moduleConfig_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestModuleConfigGet(t *testing.T) {
|
||||
strPtrEmpty := func(v string) *string { return &v }("")
|
||||
m := ModuleConfig{
|
||||
"test": map[string]*FieldCollection{
|
||||
DefaultConfigName: FieldCollectionFromData(map[string]any{
|
||||
"setindefault": DefaultConfigName,
|
||||
"setinboth": DefaultConfigName,
|
||||
}),
|
||||
"test": FieldCollectionFromData(map[string]any{
|
||||
"setinchannel": "channel",
|
||||
"setinboth": "channel",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
fields := m.GetChannelConfig("module_does_not_exist", "test")
|
||||
require.NotNil(t, fields, "must always return a valid FieldCollection")
|
||||
assert.Len(t, fields.Data(), 0)
|
||||
|
||||
fields = m.GetChannelConfig("test", "test")
|
||||
assert.Equal(t, DefaultConfigName, fields.MustString("setindefault", strPtrEmpty))
|
||||
assert.Equal(t, "channel", fields.MustString("setinchannel", strPtrEmpty))
|
||||
assert.Equal(t, "channel", fields.MustString("setinboth", strPtrEmpty))
|
||||
|
||||
fields = m.GetChannelConfig("test", "channel_not_configured")
|
||||
assert.Equal(t, DefaultConfigName, fields.MustString("setindefault", strPtrEmpty))
|
||||
assert.Equal(t, "", fields.MustString("setinchannel", strPtrEmpty))
|
||||
assert.Equal(t, DefaultConfigName, fields.MustString("setinboth", strPtrEmpty))
|
||||
}
|
|
@ -11,10 +11,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
)
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/backoff"
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
|
@ -48,6 +48,7 @@ import (
|
|||
"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/template/strings"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/template/subscriber"
|
||||
twitchFns "github.com/Luzifer/twitch-bot/v3/internal/template/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
|
@ -93,6 +94,7 @@ var (
|
|||
slice.Register,
|
||||
strings.Register,
|
||||
subscriber.Register,
|
||||
twitchFns.Register,
|
||||
|
||||
// API-only modules
|
||||
customevent.Register,
|
||||
|
@ -148,10 +150,6 @@ func registerRoute(route plugins.HTTPRouteRegistrationArgs) error {
|
|||
|
||||
func getRegistrationArguments() plugins.RegistrationArguments {
|
||||
return plugins.RegistrationArguments{
|
||||
CreateEvent: func(evt string, eventData *plugins.FieldCollection) error {
|
||||
handleMessage(ircHdl.Client(), nil, &evt, eventData)
|
||||
return nil
|
||||
},
|
||||
FormatMessage: formatMessage,
|
||||
FrontendNotify: func(mt string) { frontendNotifyHooks.Ping(mt) },
|
||||
GetDatabaseConnector: func() database.Connector { return db },
|
||||
|
@ -170,6 +168,15 @@ func getRegistrationArguments() plugins.RegistrationArguments {
|
|||
SendMessage: sendMessage,
|
||||
ValidateToken: validateAuthToken,
|
||||
|
||||
CreateEvent: func(evt string, eventData *plugins.FieldCollection) error {
|
||||
handleMessage(ircHdl.Client(), nil, &evt, eventData)
|
||||
return nil
|
||||
},
|
||||
|
||||
GetModuleConfigForChannel: func(module, channel string) *plugins.FieldCollection {
|
||||
return config.ModuleConfig.GetChannelConfig(module, channel)
|
||||
},
|
||||
|
||||
GetTwitchClientForChannel: func(channel string) (*twitch.Client, error) {
|
||||
return accessService.GetTwitchClientForChannel(channel, access.ClientConfig{
|
||||
TwitchClient: cfg.TwitchClient,
|
||||
|
|
|
@ -15,6 +15,7 @@ var (
|
|||
twitch.ScopeClipsEdit: "create clips on behalf of this user",
|
||||
twitch.ScopeModeratorReadFollowers: "see who follows this channel",
|
||||
twitch.ScopeModeratorReadShoutouts: "see shoutouts created / received",
|
||||
twitch.ScopeUserManageWhispers: "send whispers on behalf of this user",
|
||||
}
|
||||
|
||||
botDefaultScopes = []string{
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/go-irc/irc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/irc.v4"
|
||||
|
||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||
)
|
||||
|
@ -63,7 +63,7 @@ func generateTplDocsRender(e *plugins.TemplateFuncDocumentationExample) (string,
|
|||
User: "exampleuser",
|
||||
Host: "exampleuser.tmi.twitch.tv",
|
||||
},
|
||||
Tags: map[string]irc.TagValue{
|
||||
Tags: map[string]string{
|
||||
"badge-info": "subscriber/26",
|
||||
"badges": "moderator/1,subscriber/24",
|
||||
"color": "#8A2BE2",
|
||||
|
|
|
@ -67,13 +67,4 @@ Example:
|
|||
{{ end -}}
|
||||
{{- end -}}
|
||||
|
||||
## Upgrade from `v2.x` to `v3.x`
|
||||
|
||||
When adding [sprig](https://masterminds.github.io/sprig/) function collection some functions collided and needed replacement. You need to adapt your templates accordingly:
|
||||
|
||||
- Math functions (`add`, `div`, `mod`, `mul`, `multiply`, `sub`) were replaced with their sprig-equivalent and are now working with integers instead of floats. If you need them to continue to work with floats you need to use their [float-variants](https://masterminds.github.io/sprig/mathf.html).
|
||||
- `now` does no longer format the current date as a string but return the current date. You need to replace this: `now "2006-01-02"` becomes `now | date "2006-01-02"`.
|
||||
- `concat` is now used to concat arrays. To join strings you will need to modify your code: `concat ":" "string1" "string2"` becomes `lists "string1" "string2" | join ":"`.
|
||||
- `toLower` / `toUpper` need to be replaced with their sprig equivalent `lower` and `upper`.
|
||||
|
||||
{{ if false }}<!-- vim: set ft=markdown: -->{{ end }}
|
||||
|
|
Loading…
Reference in a new issue