mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 20:01:17 +00:00
[templating] Add multiply
and seededRandom
template functions
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
c6fa5a9df9
commit
c884a7c532
5 changed files with 326 additions and 31 deletions
10
internal/template/numeric/numeric.go
Normal file
10
internal/template/numeric/numeric.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package numeric
|
||||||
|
|
||||||
|
import "github.com/Luzifer/twitch-bot/plugins"
|
||||||
|
|
||||||
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
|
args.RegisterTemplateFunction("multiply", plugins.GenericTemplateFunctionGetter(multiply))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiply(m1, m2 float64) float64 { return m1 * m2 }
|
43
internal/template/random/random.go
Normal file
43
internal/template/random/random.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package random
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5" // #nosec G501 // Unly used to convert a string into a numer, no need for cryptographic safety
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/Luzifer/twitch-bot/plugins"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
|
args.RegisterTemplateFunction("seededRandom", plugins.GenericTemplateFunctionGetter(stableRandomFromSeed))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stableRandomFromSeed(seed string) (float64, error) {
|
||||||
|
seedValue, err := stringToSeed(seed)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "generating seed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rand.New(rand.NewSource(seedValue)).Float64(), nil // #nosec G404 // Only used for generating a random number from static string, no need for cryptographic safety
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToSeed(s string) (int64, error) {
|
||||||
|
hash := md5.New() // #nosec G401 // Unly used to convert a string into a numer, no need for cryptographic safety
|
||||||
|
if _, err := fmt.Fprint(hash, s); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "writing string to hasher")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hashSum = hash.Sum(nil)
|
||||||
|
sum int64
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(hashSum); i++ {
|
||||||
|
sum += int64(float64(hashSum[len(hashSum)-1-i]%10) * math.Pow(10, float64(i))) //nolint:gomnd // No need to put the 10 of 10**i into a constant named "ten"
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum, nil
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ import (
|
||||||
"github.com/Luzifer/twitch-bot/internal/actors/respond"
|
"github.com/Luzifer/twitch-bot/internal/actors/respond"
|
||||||
"github.com/Luzifer/twitch-bot/internal/actors/timeout"
|
"github.com/Luzifer/twitch-bot/internal/actors/timeout"
|
||||||
"github.com/Luzifer/twitch-bot/internal/actors/whisper"
|
"github.com/Luzifer/twitch-bot/internal/actors/whisper"
|
||||||
|
"github.com/Luzifer/twitch-bot/internal/template/numeric"
|
||||||
|
"github.com/Luzifer/twitch-bot/internal/template/random"
|
||||||
"github.com/Luzifer/twitch-bot/plugins"
|
"github.com/Luzifer/twitch-bot/plugins"
|
||||||
"github.com/Luzifer/twitch-bot/twitch"
|
"github.com/Luzifer/twitch-bot/twitch"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -23,7 +25,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
coreActorRegistations = []plugins.RegisterFunc{
|
corePluginRegistrations = []plugins.RegisterFunc{
|
||||||
|
// Actors
|
||||||
ban.Register,
|
ban.Register,
|
||||||
delay.Register,
|
delay.Register,
|
||||||
deleteactor.Register,
|
deleteactor.Register,
|
||||||
|
@ -35,13 +38,17 @@ var (
|
||||||
respond.Register,
|
respond.Register,
|
||||||
timeout.Register,
|
timeout.Register,
|
||||||
whisper.Register,
|
whisper.Register,
|
||||||
|
|
||||||
|
// Template functions
|
||||||
|
numeric.Register,
|
||||||
|
random.Register,
|
||||||
}
|
}
|
||||||
knownModules []string
|
knownModules []string
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCorePlugins() error {
|
func initCorePlugins() error {
|
||||||
args := getRegistrationArguments()
|
args := getRegistrationArguments()
|
||||||
for _, rf := range coreActorRegistations {
|
for _, rf := range corePluginRegistrations {
|
||||||
if err := rf(args); err != nil {
|
if err := rf(args); err != nil {
|
||||||
return errors.Wrap(err, "registering core plugin")
|
return errors.Wrap(err, "registering core plugin")
|
||||||
}
|
}
|
29
wiki/Home.md
29
wiki/Home.md
|
@ -140,35 +140,6 @@ rules: # See below for examples
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Templating
|
|
||||||
|
|
||||||
There are certain variables available in the strings with templating enabled:
|
|
||||||
|
|
||||||
- `channel` - Channel the message was sent to, only available for regular messages not events
|
|
||||||
- `msg` - The message object, used in functions, should not be sent to chat
|
|
||||||
- `permitTimeout` - Value of `permit_timeout` in seconds
|
|
||||||
- `username` - The username of the message author
|
|
||||||
|
|
||||||
Additionally there are some functions available in the templates:
|
|
||||||
|
|
||||||
- `arg <idx>` - Takes the message sent to the channel, splits by space and returns the Nth element
|
|
||||||
- `botHasBadge <badge>` - Checks whether bot has the given badge in the current channel
|
|
||||||
- `channelCounter <counter name>` - Wraps the counter name into a channel specific counter name including the channel name
|
|
||||||
- `concat <delimiter> <...parts>` - Join the given string parts with delimiter
|
|
||||||
- `counterValue <counter name>` - Returns the current value of the counter which identifier was supplied
|
|
||||||
- `displayName <username> [fallback]` - Returns the display name the specified user set for themselves
|
|
||||||
- `fixUsername <username>` - Ensures the username no longer contains the `@` or `#` prefix
|
|
||||||
- `formatDuration <duration> <hours> <minutes> <seconds>` - Returns a formated duration. Pass empty strings to leave out the part: `{{ formatDuration .dur "hours" "minutes" "" }}` yields `N hours, M minutes`
|
|
||||||
- `followDate <from> <to>` - Looks up when `from` followed `to`
|
|
||||||
- `group <idx> [fallback]` - Gets matching group specified by index from `match_message` regular expression, when `fallback` is defined, it is used when group has an empty match
|
|
||||||
- `lastQuoteIndex` - Gets the last quote index in the quote database for the current channel
|
|
||||||
- `recentGame <username> [fallback]` - 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.
|
|
||||||
- `streamUptime <username>` - Returns the duration the stream is online (causes an error if no current stream is found)
|
|
||||||
- `tag <tagname>` - Takes the message sent to the channel, returns the value of the tag specified
|
|
||||||
- `toLower <string>` - Converts the given string to lower-case
|
|
||||||
- `toUpper <string>` - Converts the given string to upper-case
|
|
||||||
- `variable <name> [default]` - Returns the variable value or default in case it is empty
|
|
||||||
|
|
||||||
## Command executions
|
## Command executions
|
||||||
|
|
||||||
Your command will get a JSON object passed through `stdin` you can parse to gain details about the message. It is expected to yield an array of actions on `stdout` and exit with status `0`. If it does not the action will be marked failed. In case you need to output debug output you can use `stderr` which is directly piped to the bots `stderr`.
|
Your command will get a JSON object passed through `stdin` you can parse to gain details about the message. It is expected to yield an array of actions on `stdout` and exit with status `0`. If it does not the action will be marked failed. In case you need to output debug output you can use `stderr` which is directly piped to the bots `stderr`.
|
||||||
|
|
264
wiki/Templating.md
Normal file
264
wiki/Templating.md
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
## Templating
|
||||||
|
|
||||||
|
Generally speaking the templating uses [Golang `text/template`](https://pkg.go.dev/text/template) template syntax. All fields with templating enabled do support the full synax from the `text/template` package.
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
|
||||||
|
There are certain variables available in the strings with templating enabled:
|
||||||
|
|
||||||
|
- `channel` - Channel the message was sent to, only available for regular messages not events
|
||||||
|
- `msg` - The message object, used in functions, should not be sent to chat
|
||||||
|
- `permitTimeout` - Value of `permit_timeout` in seconds
|
||||||
|
- `username` - The username of the message author
|
||||||
|
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
Additionally to the built-in functions there are extra functions available in the templates:
|
||||||
|
|
||||||
|
Examples below are using this syntax in the code block:
|
||||||
|
|
||||||
|
```
|
||||||
|
! Message matcher used for the input message
|
||||||
|
> Input message if used in the example
|
||||||
|
# Template used in the fields
|
||||||
|
< Output from the template
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `arg`
|
||||||
|
|
||||||
|
Takes the message sent to the channel, splits by space and returns the Nth element
|
||||||
|
|
||||||
|
Syntax: `arg <index>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
> !bsg @tester
|
||||||
|
# {{ arg 1 }} please refrain from BSG
|
||||||
|
< @tester please refrain from BSG
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `botHasBadge`
|
||||||
|
|
||||||
|
Checks whether bot has the given badge in the current channel
|
||||||
|
|
||||||
|
Syntax: `botHasBadge <badge>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ botHasBadge "moderator" }}
|
||||||
|
< true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `channelCounter`
|
||||||
|
|
||||||
|
Wraps the counter name into a channel specific counter name including the channel name
|
||||||
|
|
||||||
|
Syntax: `channelCounter <counter name>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ channelCounter "test" }}
|
||||||
|
< 5
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `concat`
|
||||||
|
|
||||||
|
Join the given string parts with delimiter
|
||||||
|
|
||||||
|
Syntax: `concat <delimiter> <...parts>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ concat ":" "test" .username }}
|
||||||
|
< test:luziferus
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `counterValue`
|
||||||
|
|
||||||
|
Returns the current value of the counter which identifier was supplied
|
||||||
|
|
||||||
|
Syntax: `counterValue <counter name>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ counterValue (concat ":" .channel "test") }}
|
||||||
|
< 5
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `displayName`
|
||||||
|
|
||||||
|
Returns the display name the specified user set for themselves
|
||||||
|
|
||||||
|
Syntax: `displayName <username> [fallback]`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}
|
||||||
|
< Luziferus - foobar
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `fixUsername`
|
||||||
|
|
||||||
|
Ensures the username no longer contains the `@` or `#` prefix
|
||||||
|
|
||||||
|
Syntax: `fixUsername <username>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ fixUsername .channel }} - {{ fixUsername "@luziferus" }}
|
||||||
|
< luziferus - luziferus
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `formatDuration`
|
||||||
|
|
||||||
|
Returns a formated duration. Pass empty strings to leave out the specific duration part.
|
||||||
|
|
||||||
|
Syntax: `formatDuration <duration> <hours> <minutes> <seconds>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ formatDuration (streamUptime .channel) "hours" "minutes" "seconds" }} - {{ formatDuration (streamUptime .channel) "hours" "minutes" "" }}
|
||||||
|
< 5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `followDate`
|
||||||
|
|
||||||
|
Looks up when `from` followed `to`
|
||||||
|
|
||||||
|
Syntax: `followDate <from> <to>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ followDate "tezrian" "luziferus" }}
|
||||||
|
< 2021-04-10 16:07:07 +0000 UTC
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `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
|
||||||
|
|
||||||
|
Syntax: `group <idx> [fallback]`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
! !command ([0-9]+) ([a-z]+) ([a-z]*)
|
||||||
|
> !command 12 test
|
||||||
|
# {{ group 2 "oops" }} - {{ group 3 "oops" }}
|
||||||
|
< test - oops
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `lastQuoteIndex`
|
||||||
|
|
||||||
|
Gets the last quote index in the quote database for the current channel
|
||||||
|
|
||||||
|
Syntax: `lastQuoteIndex`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Last Quote: #{{ lastQuoteIndex }}
|
||||||
|
< Last Quote: #32
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `multiply`
|
||||||
|
|
||||||
|
Returns float from calculation: `float1 * float2`
|
||||||
|
|
||||||
|
Syntax: `multiply <float1> <float2>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ printf "%.0f" (multiply 100 (seededRandom "test")) }}%
|
||||||
|
< 35%
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `recentGame`
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}
|
||||||
|
< Metro Exodus - none
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `streamUptime`
|
||||||
|
|
||||||
|
Returns the duration the stream is online (causes an error if no current stream is found)
|
||||||
|
|
||||||
|
Syntax: `streamUptime <username>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}
|
||||||
|
< 3 hours, 56 minutes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `seededRandom`
|
||||||
|
|
||||||
|
Returns a float value stable for the given seed
|
||||||
|
|
||||||
|
Syntax: `seededRandom <string-seed>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Your int this hour: {{ printf "%.0f" (multiply (seededRandom (concat ":" "int" .username (now "2006-01-02 15"))) 100) }}%
|
||||||
|
< Your int this hour: 17%
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `tag`
|
||||||
|
|
||||||
|
Takes the message sent to the channel, returns the value of the tag specified
|
||||||
|
|
||||||
|
Syntax: `tag <tagname>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ tag "login" }}
|
||||||
|
< luziferus
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `toLower` / `toUpper`
|
||||||
|
|
||||||
|
Converts the given string to lower-case / upper-case
|
||||||
|
|
||||||
|
Syntax: `toLower <string>` / `toUpper <string>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ toLower "Test" }} - {{ toUpper "Test" }}
|
||||||
|
< test - TEST
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `variable`
|
||||||
|
|
||||||
|
Returns the variable value or default in case it is empty
|
||||||
|
|
||||||
|
Syntax: `variable <name> [default]`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ variable "foo" "fallback" }} - {{ variable "unsetvar" "fallback" }}
|
||||||
|
< test - fallback
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in a new issue