[core] add streamUptime / formatDuration template functions

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-10-13 14:30:45 +02:00
parent 87732995b3
commit 34d1f90540
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
4 changed files with 91 additions and 2 deletions

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
@ -58,10 +59,33 @@ func init() {
tplFuncs.Register(n, plugins.GenericTemplateFunctionGetter(f)) tplFuncs.Register(n, plugins.GenericTemplateFunctionGetter(f))
} }
tplFuncs.Register("concat", plugins.GenericTemplateFunctionGetter(func(delim string, parts ...string) string { return strings.Join(parts, delim) }))
tplFuncs.Register("formatDuration", plugins.GenericTemplateFunctionGetter(func(dur time.Duration, units ...string) string {
dLeft := dur
if len(units) == 0 {
return ""
}
var parts []string
for idx, div := range []time.Duration{time.Hour, time.Minute, time.Second} {
part := dLeft / div
dLeft -= part * div
if len(units) <= idx || units[idx] == "" {
continue
}
parts = append(parts, fmt.Sprintf("%d %s", part, units[idx]))
}
return strings.Join(parts, ", ")
}))
tplFuncs.Register("toLower", plugins.GenericTemplateFunctionGetter(strings.ToLower)) tplFuncs.Register("toLower", plugins.GenericTemplateFunctionGetter(strings.ToLower))
tplFuncs.Register("toUpper", plugins.GenericTemplateFunctionGetter(strings.ToUpper)) tplFuncs.Register("toUpper", plugins.GenericTemplateFunctionGetter(strings.ToUpper))
tplFuncs.Register("followDate", plugins.GenericTemplateFunctionGetter(func(from, to string) (time.Time, error) { return twitchClient.GetFollowDate(from, to) }))
tplFuncs.Register("concat", plugins.GenericTemplateFunctionGetter(func(delim string, parts ...string) string { return strings.Join(parts, delim) }))
tplFuncs.Register("variable", plugins.GenericTemplateFunctionGetter(func(name string, defVal ...string) string { tplFuncs.Register("variable", plugins.GenericTemplateFunctionGetter(func(name string, defVal ...string) string {
value := store.GetVariable(name) value := store.GetVariable(name)
if value == "" && len(defVal) > 0 { if value == "" && len(defVal) > 0 {

View file

@ -2,6 +2,7 @@ package main
import ( import (
"strings" "strings"
"time"
"github.com/Luzifer/twitch-bot/plugins" "github.com/Luzifer/twitch-bot/plugins"
) )
@ -16,6 +17,8 @@ func init() {
return displayName, err return displayName, err
})) }))
tplFuncs.Register("followDate", plugins.GenericTemplateFunctionGetter(func(from, to string) (time.Time, error) { return twitchClient.GetFollowDate(from, to) }))
tplFuncs.Register("recentGame", plugins.GenericTemplateFunctionGetter(func(username string, v ...string) (string, error) { tplFuncs.Register("recentGame", plugins.GenericTemplateFunctionGetter(func(username string, v ...string) (string, error) {
game, _, err := twitchClient.GetRecentStreamInfo(strings.TrimLeft(username, "#")) game, _, err := twitchClient.GetRecentStreamInfo(strings.TrimLeft(username, "#"))
if len(v) > 0 && (err != nil || game == "") { if len(v) > 0 && (err != nil || game == "") {
@ -24,4 +27,12 @@ func init() {
return game, err return game, err
})) }))
tplFuncs.Register("streamUptime", plugins.GenericTemplateFunctionGetter(func(username string) (time.Duration, error) {
si, err := twitchClient.GetCurrentStreamInfo(strings.TrimLeft(username, "#"))
if err != nil {
return 0, err
}
return time.Since(si.StartedAt), nil
}))
} }

View file

@ -39,6 +39,23 @@ type (
apiCache *APICache apiCache *APICache
} }
StreamInfo struct {
ID string `json:"id"`
UserID string `json:"user_id"`
UserLogin string `json:"user_login"`
UserName string `json:"user_name"`
GameID string `json:"game_id"`
GameName string `json:"game_name"`
Type string `json:"type"`
Title string `json:"title"`
ViewerCount int64 `json:"viewer_count"`
StartedAt time.Time `json:"started_at"`
Language string `json:"language"`
ThumbnailURL string `json:"thumbnail_url"`
TagIds []string `json:"tag_ids"`
IsMature bool `json:"is_mature"`
}
User struct { User struct {
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
ID string `json:"id"` ID string `json:"id"`
@ -253,6 +270,41 @@ func (c Client) HasLiveStream(username string) (bool, error) {
return len(payload.Data) == 1 && payload.Data[0].Type == "live", nil return len(payload.Data) == 1 && payload.Data[0].Type == "live", nil
} }
func (c Client) GetCurrentStreamInfo(username string) (*StreamInfo, error) {
cacheKey := []string{"currentStreamInfo", username}
if si := c.apiCache.Get(cacheKey); si != nil {
return si.(*StreamInfo), nil
}
id, err := c.GetIDForUsername(username)
if err != nil {
return nil, errors.Wrap(err, "getting ID for username")
}
var payload struct {
Data []*StreamInfo `json:"data"`
}
if err := c.request(
context.Background(),
http.MethodGet,
fmt.Sprintf("https://api.twitch.tv/helix/streams?user_id=%s", id),
nil,
&payload,
); err != nil {
return nil, errors.Wrap(err, "request channel info")
}
if l := len(payload.Data); l != 1 {
return nil, errors.Errorf("unexpected number of users returned: %d", l)
}
// Stream-info can be changed at any moment, cache for a short period of time
c.apiCache.Set(cacheKey, twitchMinCacheTime, payload.Data[0])
return payload.Data[0], nil
}
func (c Client) GetIDForUsername(username string) (string, error) { func (c Client) GetIDForUsername(username string) (string, error) {
cacheKey := []string{"idForUsername", username} cacheKey := []string{"idForUsername", username}
if d := c.apiCache.Get(cacheKey); d != nil { if d := c.apiCache.Get(cacheKey); d != nil {

View file

@ -137,9 +137,11 @@ Additionally there are some functions available in the templates:
- `counterValue <counter name>` - Returns the current value of the counter which identifier was supplied - `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 - `displayName <username> [fallback]` - Returns the display name the specified user set for themselves
- `fixUsername <username>` - Ensures the username no longer contains the `@` or `#` prefix - `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` - `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 - `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
- `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. - `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 - `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 - `toLower <string>` - Converts the given string to lower-case
- `toUpper <string>` - Converts the given string to upper-case - `toUpper <string>` - Converts the given string to upper-case