Compare commits

...

13 commits

Author SHA1 Message Date
4dd903288c
prepare release v3.28.1 2024-04-02 16:17:41 +02:00
9d1ede2b1e
Lint: Fix error caused by stupid linter
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-04-02 16:12:35 +02:00
60abc308fb
prepare release v3.28.0 2024-04-02 15:22:29 +02:00
e8eb6cd0f4
[templating] add humanDateDiff and formatHumanDateDiff functions
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-04-02 15:19:49 +02:00
bc9c3eeb15
[spotify] Add spotifyLink template function
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-28 18:29:39 +01:00
bb83c34344
Update dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-28 00:18:28 +01:00
f684abc29f
Lint: Update linter config, remove no longer required exeptions
which might be a false-negative and re-added later

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-28 00:15:53 +01:00
1d4cbd9a66
[eventsub] Fix: Properly handle 409 error
by fetching the existing subscription instead of failing to access the
expected response which is not there in case of a collision

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-28 00:09:28 +01:00
0e4a963bc7
[eventsub] Fix: Twitch renamed field in adbreak_begin
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-25 22:23:31 +01:00
3f376cb2ce
[docs] Fix: Add missing documentation for adbreak_begin
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-25 22:13:13 +01:00
46db72b2cc
[overlays] Reduce socket abnormal closure to warning
resolves TWITCH-BOT-A

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-23 12:16:10 +01:00
c3085ea7f9
[eventsub] Suppress error on abnormal closure and reconnect
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-23 12:03:39 +01:00
031ad331e4
[eventsub] Fix: Do not retry subscription on collision
resolves #TWITCH-BOT-7

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-22 21:29:07 +01:00
30 changed files with 594 additions and 106 deletions

View file

@ -9,7 +9,9 @@ run:
modules-download-mode: readonly
output:
format: tab
formats:
- format: tab
path: stdout
issues:
# This disables the included exclude-list in golangci-lint as that

View file

@ -1,3 +1,38 @@
# 3.28.1 / 2024-04-02
* New Features
* [spotify] Add `spotifyLink` template function
* [templating] add `humanDateDiff` and `formatHumanDateDiff` functions
* Improvements
* [eventsub] Suppress error on abnormal closure and reconnect
* [overlays] Lower socket abnormal closure log-level to warning
* Bugfixes
* [core] Update dependencies
* [docs] Fix: Add missing documentation for `adbreak_begin`
* [eventsub] Fix: Do not retry subscription on collision
* [eventsub] Fix: Twitch renamed field in `adbreak_begin`
> [!NOTE]
> Re-release of v3.28.0 as of broken tests in that release, no functional changes.
# 3.28.0 / 2024-04-02
* New Features
* [spotify] Add `spotifyLink` template function
* [templating] add `humanDateDiff` and `formatHumanDateDiff` functions
* Improvements
* [eventsub] Suppress error on abnormal closure and reconnect
* [overlays] Lower socket abnormal closure log-level to warning
* Bugfixes
* [core] Update dependencies
* [docs] Fix: Add missing documentation for `adbreak_begin`
* [eventsub] Fix: Do not retry subscription on collision
* [eventsub] Fix: Twitch renamed field in `adbreak_begin`
# 3.27.0 / 2024-03-20
* New Features

View file

@ -2,6 +2,17 @@
title: "Available Events"
---
## `adbreak_begin`
Ad-break has begun and ads are playing now in mentioned channel.
Fields:
- `channel` - The channel the event occurred in
- `duration` - Duration of the ads in seconds
- `is_automatic` - Were the ads started by the ad-manager?
- `started_at` - When did the ad-break start
## `ban`
Moderator action caused a user to be banned from chat.

View file

@ -256,6 +256,19 @@ Example:
< 5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes
```
### `formatHumanDateDiff`
Formats a DateInterval object according to the format (%Y, %M, %D, %H, %I, %S for years, months, days, hours, minutes, seconds - Lowercase letters without leading zeros)
Syntax: `formatHumanDateDiff <format> <obj>`
Example:
```
# {{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") | formatHumanDateDiff "%Y years, %M months, %D days" }}
< 01 years, 03 months, 25 days
```
### `group`
Gets matching group specified by index from `match_message` regular expression, when `fallback` is defined, it is used when group has an empty match
@ -271,6 +284,19 @@ Example:
< test - oops
```
### `humanDateDiff`
Returns a DateInterval object describing the time difference between a and b in a "human" way of counting the time (2023-02-05 -> 2024-03-05 = 1 Year, 1 Month)
Syntax: `humanDateDiff <a> <b>`
Example:
```
# {{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") }}
< {1 3 25 23 0 0}
```
### `idForUsername`
Returns the user-id for the given username
@ -441,7 +467,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: 88%
< Your int this hour: 70%
```
### `spotifyCurrentPlaying`
@ -459,6 +485,21 @@ Example:
* Beast in Black - Die By The Blade
```
### `spotifyLink`
Retrieves the link for the playing track for the given channel
Syntax: `spotifyLink <channel>`
Example:
```
! ^!spotifylink
> !spotifylink
# {{ spotifyLink .channel }}
* https://open.spotify.com/track/3HCzXf0lNpekSqsGBcGrCd
```
### `streamUptime`
Returns the duration the stream is online (causes an error if no current stream is found)

45
go.mod
View file

@ -4,14 +4,14 @@ go 1.21
require (
github.com/Luzifer/go-openssl/v4 v4.2.2
github.com/Luzifer/go_helpers/v2 v2.22.0
github.com/Luzifer/go_helpers/v2 v2.23.0
github.com/Luzifer/korvike/functions v0.11.0
github.com/Luzifer/rconfig/v2 v2.5.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/getsentry/sentry-go v0.27.0
github.com/glebarez/sqlite v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-sql-driver/mysql v1.7.1
github.com/go-sql-driver/mysql v1.8.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gofrs/uuid/v3 v3.1.2
github.com/gorilla/mux v1.8.1
@ -22,20 +22,21 @@ require (
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb
golang.org/x/crypto v0.19.0
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/crypto v0.21.0
golang.org/x/net v0.22.0
golang.org/x/oauth2 v0.18.0
gopkg.in/irc.v4 v4.0.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.4
gorm.io/driver/postgres v1.5.6
gorm.io/gorm v1.25.7
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.8
)
require (
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
@ -46,13 +47,12 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@ -64,13 +64,13 @@ require (
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/api v1.12.0 // indirect
github.com/hashicorp/vault/api v1.12.2 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.3 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@ -89,21 +89,22 @@ 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.2.1 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/appengine v1.4.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/libc v1.49.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.29.1 // indirect
modernc.org/sqlite v1.29.5 // indirect
)

121
go.sum
View file

@ -1,11 +1,13 @@
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=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc=
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc=
github.com/Luzifer/go_helpers/v2 v2.22.0 h1:rJrZkJDzAiq4J0RUbwPI7kQ5rUy7BYQ/GUpo3fSM0y0=
github.com/Luzifer/go_helpers/v2 v2.22.0/go.mod h1:cIIqMPu3NT8/6kHke+03hVznNDLLKVGA74Lz47CWJyA=
github.com/Luzifer/go_helpers/v2 v2.23.0 h1:VowDwOCl6nOt+GVqKUX/do6a94pEeqNTRHb29MsoGX4=
github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk=
github.com/Luzifer/korvike/functions v0.11.0 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q=
github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU=
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
@ -49,8 +51,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
@ -58,8 +60,8 @@ github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
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=
@ -72,12 +74,12 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@ -90,11 +92,15 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
@ -144,8 +150,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4=
github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck=
github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE=
github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@ -163,8 +169,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-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -248,8 +254,8 @@ 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.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
@ -261,35 +267,34 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb h1:G0Rrif8QdbAz7Xy53H4Xumy6TuyKHom8pu8z/jdLwwM=
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb/go.mod h1:398xiAftMV/w8frjipnUzjr/WQ+E2fnGRv9yXobxyyk=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -302,10 +307,12 @@ 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -320,7 +327,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -338,23 +344,29 @@ 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/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.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -369,18 +381,23 @@ 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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -400,20 +417,36 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU=
gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo=
gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/cc/v4 v4.19.5 h1:QlsZyQ1zf78DGeqnQ9ILi9hXyMdoC5e1qoGNUyBjHQw=
modernc.org/cc/v4 v4.19.5/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.13.1 h1:qBttaSxEHNze36VBivw1/vkHuyjMDN3RY5wQX+p1Oxg=
modernc.org/ccgo/v4 v4.13.1/go.mod h1:Td6RI9W9G2ZpKHaJ7UeGEiB2aIpoDqLBnm4wtkbJTbQ=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.49.0 h1:/kkNBuCXvlTbOGwrQdgR67eK1Y9+kR+fhdBd89C64VM=
modernc.org/libc v1.49.0/go.mod h1:DNz0lgQgT6FPIPm8rHtjFj0FL5/YOr/NYFXWYBcSxMw=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -34,7 +34,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("counter", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &counter{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &counter{})
})
formatMessage = args.FormatMessage

View file

@ -40,7 +40,7 @@ func Register(args plugins.RegistrationArguments) error {
}
args.RegisterCopyDatabaseFunc("punish", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &punishLevel{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &punishLevel{})
})
botTwitchClient = args.GetTwitchClient()

View file

@ -94,7 +94,7 @@ func getPunishment(db database.Connector, channel, user, uuid string) (*levelCon
err := helpers.Retry(func() error {
err := db.DB().First(&p, "key = ?", getDBKey(channel, user, uuid)).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(err) //nolint:wrapcheck // we get our internal error
return backoff.NewErrCannotRetry(err)
}
return err
})

View file

@ -36,7 +36,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("quote", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &quote{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &quote{})
})
formatMessage = args.FormatMessage

View file

@ -11,17 +11,17 @@ import (
"golang.org/x/oauth2"
)
func getCurrentTrackForChannel(channel string) (track string, err error) {
func getCurrentTrackForChannel(channel string) (track currentPlayingTrackResponse, err error) {
channel = strings.TrimLeft(channel, "#")
conf, err := oauthConfig(channel, "")
if err != nil {
return "", fmt.Errorf("getting oauth config: %w", err)
return track, fmt.Errorf("getting oauth config: %w", err)
}
var token *oauth2.Token
if err = db.ReadEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), &token); err != nil {
return "", fmt.Errorf("loading oauth token: %w", err)
return track, fmt.Errorf("loading oauth token: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), spotifyRequestTimeout)
@ -29,12 +29,12 @@ func getCurrentTrackForChannel(channel string) (track string, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.spotify.com/v1/me/player/currently-playing", nil)
if err != nil {
return "", fmt.Errorf("creating currently-playing request: %w", err)
return track, fmt.Errorf("creating currently-playing request: %w", err)
}
resp, err := conf.Client(context.Background(), token).Do(req)
if err != nil {
return "", fmt.Errorf("executing request: %w", err)
return track, fmt.Errorf("executing request: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
@ -48,18 +48,35 @@ func getCurrentTrackForChannel(channel string) (track string, err error) {
}
}()
var payload currentPlayingTrackResponse
if err = json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return "", fmt.Errorf("decoding response: %w", err)
if err = json.NewDecoder(resp.Body).Decode(&track); err != nil {
return track, fmt.Errorf("decoding response: %w", err)
}
return track, nil
}
func getCurrentArtistTitleForChannel(channel string) (artistTitle string, err error) {
track, err := getCurrentTrackForChannel(channel)
if err != nil {
return "", fmt.Errorf("getting track info: %w", err)
}
var artistNames []string
for _, artist := range payload.Item.Artists {
for _, artist := range track.Item.Artists {
artistNames = append(artistNames, artist.Name)
}
return strings.Join([]string{
strings.Join(artistNames, ", "),
payload.Item.Name,
track.Item.Name,
}, " - "), nil
}
func getCurrentLinkForChannel(channel string) (link string, err error) {
track, err := getCurrentTrackForChannel(channel)
if err != nil {
return "", fmt.Errorf("getting track info: %w", err)
}
return track.Item.ExternalUrls.Spotify, nil
}

View file

@ -32,7 +32,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
args.RegisterTemplateFunction(
"spotifyCurrentPlaying",
plugins.GenericTemplateFunctionGetter(getCurrentTrackForChannel),
plugins.GenericTemplateFunctionGetter(getCurrentArtistTitleForChannel),
plugins.TemplateFuncDocumentation{
Name: "spotifyCurrentPlaying",
Description: "Retrieves the current playing track for the given channel",
@ -43,7 +43,24 @@ func Register(args plugins.RegistrationArguments) (err error) {
Template: "{{ spotifyCurrentPlaying .channel }}",
FakedOutput: "Beast in Black - Die By The Blade",
},
})
},
)
args.RegisterTemplateFunction(
"spotifyLink",
plugins.GenericTemplateFunctionGetter(getCurrentLinkForChannel),
plugins.TemplateFuncDocumentation{
Name: "spotifyLink",
Description: "Retrieves the link for the playing track for the given channel",
Syntax: "spotifyLink <channel>",
Example: &plugins.TemplateFuncDocumentationExample{
MatchMessage: "^!spotifylink",
MessageContent: "!spotifylink",
Template: "{{ spotifyLink .channel }}",
FakedOutput: "https://open.spotify.com/track/3HCzXf0lNpekSqsGBcGrCd",
},
},
)
if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
Description: "Starts authorization of an Spotify Account for a {channel}",

View file

@ -33,7 +33,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("variable", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &variable{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &variable{})
})
formatMessage = args.FormatMessage

View file

@ -22,7 +22,7 @@ func getVariable(db database.Connector, key string) (string, error) {
err := helpers.Retry(func() error {
err := db.DB().First(&v, "name = ?", key).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(err) //nolint:wrapcheck // we get our internal error
return backoff.NewErrCannotRetry(err)
}
return err
})

View file

@ -37,7 +37,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("custom_event", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &storedCustomEvent{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &storedCustomEvent{})
})
mc = &memoryCache{dbc: db}

View file

@ -75,7 +75,7 @@ func getEventByID(db database.Connector, eventID uint64) (socketMessage, error)
if err := helpers.Retry(func() (err error) {
err = db.DB().Where("id = ?", eventID).First(&evt).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(err) //nolint:wrapcheck // we get our internal error
return backoff.NewErrCannotRetry(err)
}
return err
}); err != nil {

View file

@ -92,7 +92,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("overlay_events", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &overlaysEvent{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &overlaysEvent{})
})
validateToken = args.ValidateToken
@ -405,8 +405,17 @@ func handleSocketSubscription(w http.ResponseWriter, r *http.Request) {
case err == nil:
// We use nil-error to close the connection
case errors.As(err, &cErr) && websocket.IsCloseError(cErr, websocket.CloseNormalClosure, websocket.CloseGoingAway):
// We don't need to log when the remote closes the websocket gracefully
case errors.As(err, &cErr):
switch cErr.Code {
case websocket.CloseAbnormalClosure:
logger.WithError(err).Warn("overlay websocket was closed abnormally")
case websocket.CloseNormalClosure, websocket.CloseGoingAway:
// We don't need to log when the remote closes the websocket gracefully
default:
logger.WithError(err).Error("message processing caused error")
}
default:
logger.WithError(err).Error("message processing caused error")

View file

@ -29,7 +29,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
}
args.RegisterCopyDatabaseFunc("raffle", func(src, target *gorm.DB) error {
return database.CopyObjects(src, target, &raffle{}, &raffleEntry{}) //nolint:wrapcheck // internal helper
return database.CopyObjects(src, target, &raffle{}, &raffleEntry{})
})
dbc = newDBClient(db)

View file

@ -23,6 +23,6 @@ func Retry(fn func() error) error {
// database and will be retried as if executed using Retry
func RetryTransaction(db *gorm.DB, fn func(tx *gorm.DB) error) error {
return Retry(func() error {
return db.Transaction(fn) //nolint:wrapcheck
return db.Transaction(fn)
})
}

View file

@ -172,7 +172,7 @@ func (s Service) GetTwitchClientForChannel(channel string, cfg ClientConfig) (*t
if err = helpers.Retry(func() error {
err = s.db.DB().First(&perm, "channel = ?", strings.TrimLeft(channel, "#")).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(ErrChannelNotAuthorized) //nolint:wrapcheck // We get our own error
return backoff.NewErrCannotRetry(ErrChannelNotAuthorized)
}
return errors.Wrap(err, "getting twitch credential from database")
}); err != nil {
@ -251,7 +251,7 @@ func (s Service) HasTokensForChannel(channel string) (bool, error) {
if err = helpers.Retry(func() error {
err = s.db.DB().First(&perm, "channel = ?", strings.TrimLeft(channel, "#")).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(ErrChannelNotAuthorized) //nolint:wrapcheck // We'll get our own error
return backoff.NewErrCannotRetry(ErrChannelNotAuthorized)
}
return errors.Wrap(err, "getting twitch credential from database")
}); err != nil {

View file

@ -103,7 +103,7 @@ func (s Service) HasTimer(id string) (bool, error) {
err := helpers.Retry(func() error {
err := s.db.DB().First(&t, "id = ? AND expires_at >= ?", id, time.Now().UTC()).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return backoff.NewErrCannotRetry(err) //nolint:wrapcheck // We'll get our own error
return backoff.NewErrCannotRetry(err)
}
return err
})

View file

@ -0,0 +1,31 @@
// Package date adds date-based helper functions for templating
package date
import (
"github.com/Luzifer/twitch-bot/v3/plugins"
)
// Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) error {
args.RegisterTemplateFunction("humanDateDiff", plugins.GenericTemplateFunctionGetter(NewInterval), plugins.TemplateFuncDocumentation{
Description: `Returns a DateInterval object describing the time difference between a and b in a "human" way of counting the time (2023-02-05 -> 2024-03-05 = 1 Year, 1 Month)`,
Syntax: "humanDateDiff <a> <b>",
Example: &plugins.TemplateFuncDocumentationExample{
Template: `{{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") }}`,
ExpectedOutput: "{1 3 25 23 0 0}",
},
})
args.RegisterTemplateFunction("formatHumanDateDiff", plugins.GenericTemplateFunctionGetter(func(format string, d Interval) string {
return d.Format(format)
}), plugins.TemplateFuncDocumentation{
Description: "Formats a DateInterval object according to the format (%Y, %M, %D, %H, %I, %S for years, months, days, hours, minutes, seconds - Lowercase letters without leading zeros)",
Syntax: "formatHumanDateDiff <format> <obj>",
Example: &plugins.TemplateFuncDocumentationExample{
Template: `{{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") | formatHumanDateDiff "%Y years, %M months, %D days" }}`,
ExpectedOutput: "01 years, 03 months, 25 days",
},
})
return nil
}

View file

@ -0,0 +1,94 @@
package date
import (
"fmt"
"strings"
"time"
)
type (
// Interval represents a human-minded diff of two dates which is in
// no way interchangeable with a time.Duration: The DateInterval
// takes each date component and subtracts them. This causes the
// 03/25 to be exactly one month distant from 02/25 even though the
// distance would be different than with 03/25 and 04/25 which is
// also exactly one month.
Interval struct {
Years int
Months int
Days int
Hours int
Minutes int
Seconds int
}
)
// NewInterval creates an Interval from two given dates
func NewInterval(a, b time.Time) (i Interval) {
var l, u time.Time
if a.Before(b) {
l, u = a.UTC(), b.UTC()
} else {
l, u = b.UTC(), a.UTC()
}
i.Years = u.Year() - l.Year()
i.Months = int(u.Month() - l.Month())
i.Days = u.Day() - l.Day()
i.Hours = u.Hour() - l.Hour()
i.Minutes = u.Minute() - l.Minute()
i.Seconds = u.Second() - l.Second()
if i.Seconds < 0 {
i.Minutes, i.Seconds = i.Minutes-1, i.Seconds+60 //nolint:gomnd
}
if i.Minutes < 0 {
i.Hours, i.Minutes = i.Hours-1, i.Minutes+60 //nolint:gomnd
}
if i.Hours < 0 {
i.Days, i.Hours = i.Days-1, i.Hours+24 //nolint:gomnd
}
if i.Days < 0 {
// oh boi.
i.Months, i.Days = i.Months-1, daysInMonth(u.Year(), int(u.Month())-1)+i.Days
}
if i.Months < 0 {
i.Years, i.Months = i.Years-1, i.Months+12 //nolint:gomnd
}
return i
}
func daysInMonth(year, month int) int {
return time.Date(year, time.Month(month+1), 1, 0, 0, 0, 0, time.Local).Add(-time.Second).Day()
}
// Format takes a template string analog to a strftime string and formats
// the Interval accordingly:
//
// %Y / %y = Years with / without leading digit to 2 places
// %M / %m = Months with / without leading digit to 2 places
// %D / %d = Days with / without leading digit to 2 places
// %H / %h = Hours with / without leading digit to 2 places
// %I / %i = Minutes with / without leading digit to 2 places
// %S / %s = Seconds with / without leading digit to 2 places
func (i Interval) Format(tplString string) string {
return strings.NewReplacer(
"%Y", fmt.Sprintf("%02d", i.Years),
"%y", fmt.Sprintf("%d", i.Years),
"%M", fmt.Sprintf("%02d", i.Months),
"%m", fmt.Sprintf("%d", i.Months),
"%D", fmt.Sprintf("%02d", i.Days),
"%d", fmt.Sprintf("%d", i.Days),
"%H", fmt.Sprintf("%02d", i.Hours),
"%h", fmt.Sprintf("%d", i.Hours),
"%I", fmt.Sprintf("%02d", i.Minutes),
"%i", fmt.Sprintf("%d", i.Minutes),
"%S", fmt.Sprintf("%02d", i.Seconds),
"%s", fmt.Sprintf("%d", i.Seconds),
).Replace(tplString)
}

View file

@ -0,0 +1,134 @@
package date
import (
_ "embed"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
//go:embed tzdata
var tzDataEuropeBerlin []byte
//nolint:funlen // This is just a collection of test cases
func TestNewInterval(t *testing.T) {
tz, err := time.LoadLocationFromTZData("Europe/Berlin", tzDataEuropeBerlin)
require.NoError(t, err)
for i, tc := range []struct {
A, B time.Time
Exp Interval
}{
{
// Plain and simple: 1 Month
A: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
B: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 0, 0, 0, 0},
},
{
// Plain and simple: 1 Month, reversed
A: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
B: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 0, 0, 0, 0},
},
{
// Plain and simple: 1 Year, 1 Month
A: time.Date(2023, 2, 3, 0, 0, 0, 0, tz),
B: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
Exp: Interval{1, 1, 0, 0, 0, 0},
},
{
// 11 Months, so Year and Month needs to be adjusted
A: time.Date(2023, 3, 3, 0, 0, 0, 0, tz),
B: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
Exp: Interval{0, 11, 0, 0, 0, 0},
},
{
// Plain and simple: 2 Days
A: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
B: time.Date(2024, 3, 5, 0, 0, 0, 0, tz),
Exp: Interval{0, 0, 2, 0, 0, 0},
},
{
// 1 Month and a few days, so Month and Day needs to be adjusted
A: time.Date(2024, 3, 25, 0, 0, 0, 0, tz),
B: time.Date(2024, 5, 5, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 9, 23, 0, 0},
},
{
// 1 Month and a few days, so Month and Day needs to be adjusted
A: time.Date(2024, 2, 25, 0, 0, 0, 0, tz),
B: time.Date(2024, 4, 5, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 10, 23, 0, 0},
},
{
// 1 Month and a few days, so Month and Day needs to be adjusted
A: time.Date(2024, 1, 25, 0, 0, 0, 0, tz),
B: time.Date(2024, 3, 5, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 9, 0, 0, 0},
},
{
// 1 Month and a few days, so Month and Day needs to be adjusted
A: time.Date(2023, 1, 25, 0, 0, 0, 0, tz),
B: time.Date(2023, 3, 5, 0, 0, 0, 0, tz),
Exp: Interval{0, 1, 8, 0, 0, 0},
},
{
// 1 Day and a few hours, so Day and Hours needs to be adjusted
A: time.Date(2024, 3, 5, 14, 0, 0, 0, tz),
B: time.Date(2024, 3, 7, 0, 0, 0, 0, tz),
Exp: Interval{0, 0, 1, 10, 0, 0},
},
{
// 1 Hour and a few minutes, so Hours and Minutes needs to be adjusted
A: time.Date(2024, 3, 5, 14, 25, 0, 0, tz),
B: time.Date(2024, 3, 5, 16, 12, 0, 0, tz),
Exp: Interval{0, 0, 0, 1, 47, 0},
},
{
// 1 Minute and a few seconds, so Minutes and Seconds needs to be adjusted
A: time.Date(2024, 3, 5, 14, 25, 13, 0, tz),
B: time.Date(2024, 3, 5, 14, 27, 0, 0, tz),
Exp: Interval{0, 0, 0, 0, 1, 47},
},
{
// Nearly one year but a few seconds, everything needs to be adjusted
A: time.Date(2024, 3, 5, 14, 25, 0, 0, tz),
B: time.Date(2023, 3, 5, 14, 25, 13, 0, tz),
Exp: Interval{0, 11, 28, 23, 59, 47},
},
{
// Nearly one year but a few seconds, everything needs to be adjusted
A: time.Date(2024, 8, 5, 14, 25, 0, 0, tz),
B: time.Date(2023, 8, 5, 14, 25, 13, 0, tz),
Exp: Interval{0, 11, 30, 23, 59, 47},
},
{
// Nearly one year but a few seconds, everything needs to be adjusted
A: time.Date(2024, 7, 5, 14, 25, 0, 0, tz),
B: time.Date(2023, 7, 5, 14, 25, 13, 0, tz),
Exp: Interval{0, 11, 29, 23, 59, 47},
},
{
// Nearly one year but a few seconds, everything needs to be adjusted
A: time.Date(2024, 2, 5, 14, 25, 0, 0, tz),
B: time.Date(2023, 2, 5, 14, 25, 13, 0, tz),
Exp: Interval{0, 11, 30, 23, 59, 47},
},
} {
assert.Equal(t,
tc.Exp,
NewInterval(tc.A, tc.B),
fmt.Sprintf("%d: %s -> %s", i, tc.A, tc.B))
}
}
func TestFormatInterval(t *testing.T) {
ti := Interval{1, 2, 3, 4, 5, 6}
assert.Equal(t,
"01 1 years 02 2 months 03 3 days 04 4 hours 05 5 minutes 06 6 seconds",
ti.Format("%Y %y years %M %m months %D %d days %H %h hours %I %i minutes %S %s seconds"))
}

Binary file not shown.

View file

@ -6,10 +6,12 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/mitchellh/hashstructure/v2"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Collection of known EventSub event-types
@ -60,7 +62,7 @@ type (
// EventSubEventAdBreakBegin contains the payload for an AdBreak event
EventSubEventAdBreakBegin struct {
Duration int64 `json:"duration_seconds"`
Timestamp time.Time `json:"timestamp"`
StartedAt time.Time `json:"started_at"`
IsAutomatic bool `json:"is_automatic"`
BroadcasterUserID string `json:"broadcaster_user_id"`
BroadcasterUserLogin string `json:"broadcaster_user_login"`
@ -277,8 +279,10 @@ func (c *Client) createEventSubSubscriptionWebsocket(ctx context.Context, sub ev
func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType, sub eventSubSubscription) (*eventSubSubscription, error) {
var (
buf = new(bytes.Buffer)
resp struct {
buf = new(bytes.Buffer)
err error
mustFetchSubsctiption bool
resp struct {
Total int64 `json:"total"`
Data []eventSubSubscription `json:"data"`
Pagination struct {
@ -287,20 +291,73 @@ func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType,
}
)
if err := json.NewEncoder(buf).Encode(sub); err != nil {
conHash, err := sub.Condition.Hash()
if err != nil {
return nil, fmt.Errorf("hashing input condition: %w", err)
}
if err = json.NewEncoder(buf).Encode(sub); err != nil {
return nil, errors.Wrap(err, "assemble subscribe payload")
}
if err := c.Request(ctx, ClientRequestOpts{
if err = c.Request(ctx, ClientRequestOpts{
AuthType: auth,
Body: buf,
Method: http.MethodPost,
OKStatus: http.StatusAccepted,
Out: &resp,
URL: "https://api.twitch.tv/helix/eventsub/subscriptions",
ValidateFunc: func(opts ClientRequestOpts, resp *http.Response) error {
if resp.StatusCode == http.StatusConflict {
// This is fine: We needed that subscription, it exists
mustFetchSubsctiption = true
return nil
}
// Fallback to default handling
return ValidateStatus(opts, resp)
},
}); err != nil {
return nil, errors.Wrap(err, "executing request")
return nil, fmt.Errorf("creating subscription: %w", err)
}
return &resp.Data[0], nil
if mustFetchSubsctiption {
params := make(url.Values)
params.Set("status", "enabled")
params.Set("type", sub.Type)
if err = c.Request(ctx, ClientRequestOpts{
AuthType: auth,
Method: http.MethodGet,
OKStatus: http.StatusOK,
Out: &resp,
URL: fmt.Sprintf("https://api.twitch.tv/helix/eventsub/subscriptions?%s", params.Encode()),
}); err != nil {
return nil, fmt.Errorf("fetching subscription: %w", err)
}
}
for i := range resp.Data {
s := resp.Data[i]
if s.Type != sub.Type || s.Version != sub.Version {
// Not the subscription we're searching for
continue
}
sConHash, err := s.Condition.Hash()
if err != nil {
logrus.WithError(err).Error("hashing eventsub subscription condition")
continue
}
if sConHash != conHash {
// Different conditions
continue
}
return &s, nil
}
return nil, fmt.Errorf("no subscription matching input found")
}

View file

@ -385,6 +385,10 @@ func (e *EventSubSocketClient) handleSocketError(err error, msgC chan eventSubSo
e.logger.Debug("websocket was closed normally")
return nil
case websocket.CloseAbnormalClosure:
e.logger.Warn("websocket reported abnormal closure")
return errors.Wrap(e.connect(e.socketDest, msgC, errC, "network-error"), "re-connecting after abnormal closure")
default:
// Some non-twitch close code we did not expect
e.logger.WithError(closeErr).Error("websocket reported unexpected error code")
@ -441,7 +445,7 @@ func (e *EventSubSocketClient) retryBackgroundSubscribe(st eventSubSocketSubscri
if err := e.runCtx.Err(); err != nil {
// Our run-context was cancelled, stop retrying to subscribe
// to topics as this client was closed
return backoff.NewErrCannotRetry(err) //nolint:wrapcheck // We get our internal error
return backoff.NewErrCannotRetry(err)
}
return e.subscribe(st)

View file

@ -46,6 +46,7 @@ import (
"github.com/Luzifer/twitch-bot/v3/internal/apimodules/raffle"
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
"github.com/Luzifer/twitch-bot/v3/internal/template/api"
"github.com/Luzifer/twitch-bot/v3/internal/template/date"
"github.com/Luzifer/twitch-bot/v3/internal/template/numeric"
"github.com/Luzifer/twitch-bot/v3/internal/template/random"
"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
@ -93,6 +94,7 @@ var (
// Template functions
api.Register,
date.Register,
numeric.Register,
random.Register,
slice.Register,
@ -189,7 +191,6 @@ func getRegistrationArguments() plugins.RegistrationArguments {
},
GetTwitchClientForChannel: func(channel string) (*twitch.Client, error) {
//nolint:wrapcheck // own package, no need to wrap
return accessService.GetTwitchClientForChannel(channel, access.ClientConfig{
TwitchClient: cfg.TwitchClient,
TwitchClientSecret: cfg.TwitchClientSecret,

View file

@ -80,6 +80,7 @@ func handleStatusRequest(w http.ResponseWriter, r *http.Request) {
{
Name: "Twitch Client Authorized",
Description: "Twitch Client is authorized and can fetch authorized user",
//nolint:contextcheck // Check is too stupid to see the context IS passed
checkFn: func() error {
if twitchClient == nil {
return errors.New("not initialized")

View file

@ -245,7 +245,7 @@ func (*twitchWatcher) handleEventSubChannelAdBreakBegin(m json.RawMessage) error
"channel": "#" + payload.BroadcasterUserLogin,
"duration": payload.Duration,
"is_automatic": payload.IsAutomatic,
"timestamp": payload.Timestamp,
"started_at": payload.StartedAt,
})
log.WithFields(log.Fields(fields.Data())).Info("Ad-Break started")