[templating] Add multiply and seededRandom template functions

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-11-19 22:53:30 +01:00
parent c6fa5a9df9
commit c884a7c532
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
5 changed files with 326 additions and 31 deletions

View 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 }

View 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
}

View file

@ -16,6 +16,8 @@ import (
"github.com/Luzifer/twitch-bot/internal/actors/respond"
"github.com/Luzifer/twitch-bot/internal/actors/timeout"
"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/twitch"
"github.com/pkg/errors"
@ -23,7 +25,8 @@ import (
)
var (
coreActorRegistations = []plugins.RegisterFunc{
corePluginRegistrations = []plugins.RegisterFunc{
// Actors
ban.Register,
delay.Register,
deleteactor.Register,
@ -35,13 +38,17 @@ var (
respond.Register,
timeout.Register,
whisper.Register,
// Template functions
numeric.Register,
random.Register,
}
knownModules []string
)
func initCorePlugins() error {
args := getRegistrationArguments()
for _, rf := range coreActorRegistations {
for _, rf := range corePluginRegistrations {
if err := rf(args); err != nil {
return errors.Wrap(err, "registering core plugin")
}

View file

@ -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
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
View 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
```