mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 11:51:17 +00:00
[docs] Add auto-generated template documentation (#50)
This commit is contained in:
parent
a39dc5e4c6
commit
1585df5e90
21 changed files with 630 additions and 91 deletions
5
Makefile
5
Makefile
|
@ -57,9 +57,14 @@ trivy:
|
||||||
|
|
||||||
# -- Documentation Site --
|
# -- Documentation Site --
|
||||||
|
|
||||||
|
docs: actor_docs template_docs
|
||||||
|
|
||||||
actor_docs:
|
actor_docs:
|
||||||
go run . --storage-conn-string $(shell mktemp).db actor-docs >docs/content/configuration/actors.md
|
go run . --storage-conn-string $(shell mktemp).db actor-docs >docs/content/configuration/actors.md
|
||||||
|
|
||||||
|
template_docs:
|
||||||
|
go run . --storage-conn-string $(shell mktemp).db tpl-docs >docs/content/configuration/templating.md
|
||||||
|
|
||||||
eventclient_docs:
|
eventclient_docs:
|
||||||
echo -e "---\ntitle: EventClient\nweight: 10000\n---\n" >docs/content/overlays/eventclient.md
|
echo -e "---\ntitle: EventClient\nweight: 10000\n---\n" >docs/content/overlays/eventclient.md
|
||||||
docker run --rm -i -v $(CURDIR):$(CURDIR) -w $(CURDIR) node:18-alpine sh -ec 'npx --yes jsdoc-to-markdown --files ./internal/apimodules/overlays/default/eventclient.js' >>docs/content/overlays/eventclient.md
|
docker run --rm -i -v $(CURDIR):$(CURDIR) -w $(CURDIR) node:18-alpine sh -ec 'npx --yes jsdoc-to-markdown --files ./internal/apimodules/overlays/default/eventclient.js' >>docs/content/overlays/eventclient.md
|
||||||
|
|
26
cli_tplDocs.go
Normal file
26
cli_tplDocs.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cli.Add(cliRegistryEntry{
|
||||||
|
Name: "tpl-docs",
|
||||||
|
Description: "Generate markdown documentation for available template functions",
|
||||||
|
Run: func(args []string) error {
|
||||||
|
doc, err := generateTplDocs()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "generating template docs")
|
||||||
|
}
|
||||||
|
if _, err = os.Stdout.Write(append(bytes.TrimSpace(doc), '\n')); err != nil {
|
||||||
|
return errors.Wrap(err, "writing actor docs to stdout")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -30,7 +30,8 @@ Examples below are using this syntax in the code block:
|
||||||
! Message matcher used for the input message
|
! Message matcher used for the input message
|
||||||
> Input message if used in the example
|
> Input message if used in the example
|
||||||
# Template used in the fields
|
# Template used in the fields
|
||||||
< Output from the template
|
< Output from the template (Rendered during docs generation)
|
||||||
|
* Output from the template (Static output, template not rendered)
|
||||||
```
|
```
|
||||||
|
|
||||||
### `arg`
|
### `arg`
|
||||||
|
@ -96,7 +97,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ channelCounter "test" }}
|
# {{ channelCounter "test" }}
|
||||||
< 5
|
< #example:test
|
||||||
```
|
```
|
||||||
|
|
||||||
### `counterValue`
|
### `counterValue`
|
||||||
|
@ -109,7 +110,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ counterValue (list .channel "test" | join ":") }}
|
# {{ counterValue (list .channel "test" | join ":") }}
|
||||||
< 5
|
* 5
|
||||||
```
|
```
|
||||||
|
|
||||||
### `counterValueAdd`
|
### `counterValueAdd`
|
||||||
|
@ -119,9 +120,10 @@ Adds the given value (or 1 if no value) to the counter and returns its new value
|
||||||
Syntax: `counterValueAdd <counter name> [increase=1]`
|
Syntax: `counterValueAdd <counter name> [increase=1]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ counterValueAdd "myCounter" }} {{ counterValueAdd "myCounter" 5 }}
|
# {{ counterValueAdd "myCounter" }} {{ counterValueAdd "myCounter" 5 }}
|
||||||
< 1 6
|
* 1 6
|
||||||
```
|
```
|
||||||
|
|
||||||
### `displayName`
|
### `displayName`
|
||||||
|
@ -134,7 +136,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}
|
# {{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}
|
||||||
< Luziferus - foobar
|
* Luziferus - foobar
|
||||||
```
|
```
|
||||||
|
|
||||||
### `doesFollow`
|
### `doesFollow`
|
||||||
|
@ -147,7 +149,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ doesFollow "tezrian" "luziferus" }}
|
# {{ doesFollow "tezrian" "luziferus" }}
|
||||||
< true
|
* true
|
||||||
```
|
```
|
||||||
|
|
||||||
### `doesFollowLongerThan`
|
### `doesFollowLongerThan`
|
||||||
|
@ -160,7 +162,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ doesFollowLongerThan "tezrian" "luziferus" "168h" }}
|
# {{ doesFollowLongerThan "tezrian" "luziferus" "168h" }}
|
||||||
< true
|
* true
|
||||||
```
|
```
|
||||||
|
|
||||||
### `fixUsername`
|
### `fixUsername`
|
||||||
|
@ -173,20 +175,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ fixUsername .channel }} - {{ fixUsername "@luziferus" }}
|
# {{ fixUsername .channel }} - {{ fixUsername "@luziferus" }}
|
||||||
< luziferus - luziferus
|
< example - 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `followAge`
|
### `followAge`
|
||||||
|
@ -199,7 +188,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ followAge "tezrian" "luziferus" }}
|
# {{ followAge "tezrian" "luziferus" }}
|
||||||
< 15004h14m59.116620989s
|
* 15004h14m59.116620989s
|
||||||
```
|
```
|
||||||
|
|
||||||
### `followDate`
|
### `followDate`
|
||||||
|
@ -212,7 +201,20 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ followDate "tezrian" "luziferus" }}
|
# {{ followDate "tezrian" "luziferus" }}
|
||||||
< 2021-04-10 16:07:07 +0000 UTC
|
* 2021-04-10 16:07:07 +0000 UTC
|
||||||
|
```
|
||||||
|
|
||||||
|
### `formatDuration`
|
||||||
|
|
||||||
|
Returns a formated duration. Pass empty strings to leave out the specific duration part.
|
||||||
|
|
||||||
|
Syntax: `formatDuration <duration> <hours> <minutes> <seconds>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ formatDuration .testDuration "hours" "minutes" "seconds" }} - {{ formatDuration .testDuration "hours" "minutes" "" }}
|
||||||
|
< 5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes
|
||||||
```
|
```
|
||||||
|
|
||||||
### `group`
|
### `group`
|
||||||
|
@ -224,7 +226,7 @@ Syntax: `group <idx> [fallback]`
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
! !command ([0-9]+) ([a-z]+) ([a-z]*)
|
! !command ([0-9]+) ([a-z]+) ?([a-z]*)
|
||||||
> !command 12 test
|
> !command 12 test
|
||||||
# {{ group 2 "oops" }} - {{ group 3 "oops" }}
|
# {{ group 2 "oops" }} - {{ group 3 "oops" }}
|
||||||
< test - oops
|
< test - oops
|
||||||
|
@ -234,7 +236,7 @@ Example:
|
||||||
|
|
||||||
Tests whether a string is in a given list of strings (for conditional templates).
|
Tests whether a string is in a given list of strings (for conditional templates).
|
||||||
|
|
||||||
Syntax: `inList "search" "item1" "item2" [...]`
|
Syntax: `inList <search> <...string>`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -249,15 +251,13 @@ Example:
|
||||||
|
|
||||||
Fetches remote URL and applies jq-like query to it returning the result as string. (Remote API needs to return status 200 within 5 seconds.)
|
Fetches remote URL and applies jq-like query to it returning the result as string. (Remote API needs to return status 200 within 5 seconds.)
|
||||||
|
|
||||||
Syntax: `jsonAPI "https://example.com/doc.json" ".data.exampleString" ["fallback"]`
|
Syntax: `jsonAPI <url> <jq-like path> [fallback]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
! !mycmd
|
# {{ jsonAPI "https://api.github.com/repos/Luzifer/twitch-bot" ".owner.login" }}
|
||||||
> !mycmd
|
* Luzifer
|
||||||
# {{ jsonAPI "https://example.com/doc.json" ".data.exampleString" }}
|
|
||||||
< example string
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `lastPoll`
|
### `lastPoll`
|
||||||
|
@ -270,7 +270,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Last Poll: {{ (lastPoll .channel).Title }}
|
# Last Poll: {{ (lastPoll .channel).Title }}
|
||||||
< Last Poll: Und wie siehts im Template aus?
|
* Last Poll: Und wie siehts im Template aus?
|
||||||
```
|
```
|
||||||
|
|
||||||
See schema of returned object in [`pkg/twitch/polls.go#L13`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/polls.go#L13)
|
See schema of returned object in [`pkg/twitch/polls.go#L13`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/polls.go#L13)
|
||||||
|
@ -285,7 +285,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Last Quote: #{{ lastQuoteIndex }}
|
# Last Quote: #{{ lastQuoteIndex }}
|
||||||
< Last Quote: #32
|
* Last Quote: #32
|
||||||
```
|
```
|
||||||
|
|
||||||
### `mention`
|
### `mention`
|
||||||
|
@ -310,7 +310,7 @@ Syntax: `pow <float1> <float2>`
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ printf "%.0f" (pow 10 4) }}%
|
# {{ printf "%.0f" (pow 10 4) }}
|
||||||
< 10000
|
< 10000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -324,20 +324,20 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ profileImage .username }}
|
# {{ profileImage .username }}
|
||||||
< https://static-cdn.jtvnw.net/jtv_user_pictures/[...].png
|
* https://static-cdn.jtvnw.net/jtv_user_pictures/[...].png
|
||||||
```
|
```
|
||||||
|
|
||||||
### `randomString`
|
### `randomString`
|
||||||
|
|
||||||
Randomly picks a string from a list of strings
|
Randomly picks a string from a list of strings
|
||||||
|
|
||||||
Syntax: `randomString "a" [...]`
|
Syntax: `randomString <string> [...string]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ randomString "a" "b" "c" "d" }}
|
# {{ randomString "a" "b" "c" "d" }}
|
||||||
< a
|
* a
|
||||||
```
|
```
|
||||||
|
|
||||||
### `recentGame`
|
### `recentGame`
|
||||||
|
@ -350,10 +350,9 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}
|
# {{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}
|
||||||
< Metro Exodus - none
|
* Metro Exodus - none
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### `recentTitle`
|
### `recentTitle`
|
||||||
|
|
||||||
Returns the last stream title of the specified user or the `fallback` if the title could not be fetched. If no fallback was supplied the message will fail and not be sent.
|
Returns the last stream title of the specified user or the `fallback` if the title could not be fetched. If no fallback was supplied the message will fail and not be sent.
|
||||||
|
@ -364,7 +363,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}
|
# {{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}
|
||||||
< Die Oper haben wir überlebt, mal sehen was uns sonst noch alles töten möchte… - none
|
* Die Oper haben wir überlebt, mal sehen was uns sonst noch alles töten möchte… - none
|
||||||
```
|
```
|
||||||
|
|
||||||
### `seededRandom`
|
### `seededRandom`
|
||||||
|
@ -376,8 +375,8 @@ Syntax: `seededRandom <string-seed>`
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Your int this hour: {{ printf "%.0f" (mul (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||||
< Your int this hour: 17%
|
< Your int this hour: 84%
|
||||||
```
|
```
|
||||||
|
|
||||||
### `streamUptime`
|
### `streamUptime`
|
||||||
|
@ -390,7 +389,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}
|
# {{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}
|
||||||
< 3 hours, 56 minutes
|
* 3 hours, 56 minutes
|
||||||
```
|
```
|
||||||
|
|
||||||
### `subCount`
|
### `subCount`
|
||||||
|
@ -403,7 +402,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ subCount "luziferus" }}
|
# {{ subCount "luziferus" }}
|
||||||
< 26
|
* 26
|
||||||
```
|
```
|
||||||
|
|
||||||
### `subPoints`
|
### `subPoints`
|
||||||
|
@ -416,7 +415,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ subPoints "luziferus" }}
|
# {{ subPoints "luziferus" }}
|
||||||
< 26
|
* 26
|
||||||
```
|
```
|
||||||
|
|
||||||
### `tag`
|
### `tag`
|
||||||
|
@ -428,15 +427,15 @@ Syntax: `tag <tagname>`
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ tag "login" }}
|
# {{ tag "display-name" }}
|
||||||
< luziferus
|
< ExampleUser
|
||||||
```
|
```
|
||||||
|
|
||||||
### `textAPI`
|
### `textAPI`
|
||||||
|
|
||||||
Fetches remote URL and returns the result as string. (Remote API needs to return status 200 within 5 seconds.)
|
Fetches remote URL and returns the result as string. (Remote API needs to return status 200 within 5 seconds.)
|
||||||
|
|
||||||
Syntax: `textAPI "https://example.com/" ["fallback"]`
|
Syntax: `textAPI <url> [fallback]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -444,7 +443,7 @@ Example:
|
||||||
! !weather (.*)
|
! !weather (.*)
|
||||||
> !weather Hamburg
|
> !weather Hamburg
|
||||||
# {{ textAPI (printf "https://api.scorpstuff.com/weather.php?units=metric&city=%s" (urlquery (group 1))) }}
|
# {{ textAPI (printf "https://api.scorpstuff.com/weather.php?units=metric&city=%s" (urlquery (group 1))) }}
|
||||||
< Weather for Hamburg, DE: Few clouds with a temperature of 22 C (71.6 F). [...]
|
* Weather for Hamburg, DE: Few clouds with a temperature of 22 C (71.6 F). [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
### `variable`
|
### `variable`
|
||||||
|
@ -457,7 +456,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# {{ variable "foo" "fallback" }} - {{ variable "unsetvar" "fallback" }}
|
# {{ variable "foo" "fallback" }} - {{ variable "unsetvar" "fallback" }}
|
||||||
< test - fallback
|
* test - fallback
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade from `v2.x` to `v3.x`
|
## Upgrade from `v2.x` to `v3.x`
|
||||||
|
|
17
functions.go
17
functions.go
|
@ -23,6 +23,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type templateFuncProvider struct {
|
type templateFuncProvider struct {
|
||||||
|
docs []plugins.TemplateFuncDocumentation
|
||||||
funcs map[string]plugins.TemplateFuncGetter
|
funcs map[string]plugins.TemplateFuncGetter
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ func (t *templateFuncProvider) GetFuncNames() []string {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templateFuncProvider) Register(name string, fg plugins.TemplateFuncGetter) {
|
func (t *templateFuncProvider) Register(name string, fg plugins.TemplateFuncGetter, doc ...plugins.TemplateFuncDocumentation) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
@ -81,6 +82,11 @@ func (t *templateFuncProvider) Register(name string, fg plugins.TemplateFuncGett
|
||||||
}
|
}
|
||||||
|
|
||||||
t.funcs[name] = fg
|
t.funcs[name] = fg
|
||||||
|
|
||||||
|
if len(doc) > 0 {
|
||||||
|
doc[0].Name = name
|
||||||
|
t.docs = append(t.docs, doc[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -112,5 +118,12 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(parts, ", ")
|
return strings.Join(parts, ", ")
|
||||||
}))
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns a formated duration. Pass empty strings to leave out the specific duration part.",
|
||||||
|
Syntax: "formatDuration <duration> <hours> <minutes> <seconds>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ formatDuration .testDuration "hours" "minutes" "seconds" }} - {{ formatDuration .testDuration "hours" "minutes" "" }}`,
|
||||||
|
ExpectedOutput: "5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-irc/irc"
|
"github.com/go-irc/irc"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
|
||||||
"github.com/Luzifer/twitch-bot/v3/plugins"
|
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,26 +20,42 @@ func init() {
|
||||||
|
|
||||||
return msgParts[arg], nil
|
return msgParts[arg], nil
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Takes the message sent to the channel, splits by space and returns the Nth element",
|
||||||
|
Syntax: "arg <index>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
MessageContent: "!bsg @tester",
|
||||||
|
Template: `{{ arg 1 }} please refrain from BSG`,
|
||||||
|
ExpectedOutput: `@tester please refrain from BSG`,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
tplFuncs.Register("botHasBadge", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
tplFuncs.Register("botHasBadge", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
||||||
return func(badge string) bool {
|
return func(badge string) bool {
|
||||||
channel, err := fields.String("channel")
|
badges := twitch.ParseBadgeLevels(m)
|
||||||
if err != nil {
|
return badges.Has(badge)
|
||||||
log.Trace("Fields for botHasBadge function had no channel")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
state := botUserstate.Get(channel)
|
|
||||||
if state == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.Badges.Has(badge)
|
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Checks whether bot has the given badge in the current channel",
|
||||||
|
Syntax: "botHasBadge <badge>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ botHasBadge "moderator" }}`,
|
||||||
|
ExpectedOutput: "true",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
tplFuncs.Register("fixUsername", plugins.GenericTemplateFunctionGetter(func(username string) string { return strings.TrimLeft(username, "@#") }))
|
tplFuncs.Register(
|
||||||
|
"fixUsername",
|
||||||
|
plugins.GenericTemplateFunctionGetter(func(username string) string { return strings.TrimLeft(username, "@#") }),
|
||||||
|
plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Ensures the username no longer contains the `@` or `#` prefix",
|
||||||
|
Syntax: "fixUsername <username>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ fixUsername .channel }} - {{ fixUsername "@luziferus" }}`,
|
||||||
|
ExpectedOutput: "example - luziferus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
||||||
return func(idx int, fallback ...string) (string, error) {
|
return func(idx int, fallback ...string) (string, error) {
|
||||||
|
@ -54,14 +70,41 @@ func init() {
|
||||||
|
|
||||||
return fields[idx], nil
|
return fields[idx], nil
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "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: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
MatchMessage: "!command ([0-9]+) ([a-z]+) ?([a-z]*)",
|
||||||
|
MessageContent: "!command 12 test",
|
||||||
|
Template: `{{ group 2 "oops" }} - {{ group 3 "oops" }}`,
|
||||||
|
ExpectedOutput: "test - oops",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
tplFuncs.Register("mention", plugins.GenericTemplateFunctionGetter(func(username string) string { return "@" + strings.TrimLeft(username, "@#") }))
|
tplFuncs.Register(
|
||||||
|
"mention",
|
||||||
|
plugins.GenericTemplateFunctionGetter(func(username string) string { return "@" + strings.TrimLeft(username, "@#") }),
|
||||||
|
plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Strips username and converts into a mention",
|
||||||
|
Syntax: "mention <username>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ mention "@user" }} {{ mention "user" }} {{ mention "#user" }}`,
|
||||||
|
ExpectedOutput: "@user @user @user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
tplFuncs.Register("tag", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
tplFuncs.Register("tag", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} {
|
||||||
return func(tag string) string {
|
return func(tag string) string {
|
||||||
s, _ := m.GetTag(tag)
|
s, _ := m.GetTag(tag)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Takes the message sent to the channel, returns the value of the tag specified",
|
||||||
|
Syntax: "tag <tagname>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ tag "display-name" }}`,
|
||||||
|
ExpectedOutput: "ExampleUser",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,16 +13,96 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tplFuncs.Register("displayName", plugins.GenericTemplateFunctionGetter(tplTwitchDisplayName))
|
tplFuncs.Register("displayName", plugins.GenericTemplateFunctionGetter(tplTwitchDisplayName), plugins.TemplateFuncDocumentation{
|
||||||
tplFuncs.Register("doesFollow", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollow))
|
Description: "Returns the display name the specified user set for themselves",
|
||||||
tplFuncs.Register("followAge", plugins.GenericTemplateFunctionGetter(tplTwitchFollowAge))
|
Syntax: "displayName <username> [fallback]",
|
||||||
tplFuncs.Register("followDate", plugins.GenericTemplateFunctionGetter(tplTwitchFollowDate))
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
tplFuncs.Register("doesFollowLongerThan", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollowLongerThan))
|
Template: `{{ displayName "luziferus" }} - {{ displayName "notexistinguser" "foobar" }}`,
|
||||||
tplFuncs.Register("lastPoll", plugins.GenericTemplateFunctionGetter(tplTwitchLastPoll))
|
FakedOutput: "Luziferus - foobar",
|
||||||
tplFuncs.Register("profileImage", plugins.GenericTemplateFunctionGetter(tplTwitchProfileImage))
|
},
|
||||||
tplFuncs.Register("recentGame", plugins.GenericTemplateFunctionGetter(tplTwitchRecentGame))
|
})
|
||||||
tplFuncs.Register("recentTitle", plugins.GenericTemplateFunctionGetter(tplTwitchRecentTitle))
|
|
||||||
tplFuncs.Register("streamUptime", plugins.GenericTemplateFunctionGetter(tplTwitchStreamUptime))
|
tplFuncs.Register("doesFollowLongerThan", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollowLongerThan), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns whether `from` follows `to` for more than `duration`",
|
||||||
|
Syntax: "doesFollowLongerThan <from> <to> <duration>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ doesFollowLongerThan "tezrian" "luziferus" "168h" }}`,
|
||||||
|
FakedOutput: "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("doesFollow", plugins.GenericTemplateFunctionGetter(tplTwitchDoesFollow), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns whether `from` follows `to`",
|
||||||
|
Syntax: "doesFollow <from> <to>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ doesFollow "tezrian" "luziferus" }}`,
|
||||||
|
FakedOutput: "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("followAge", plugins.GenericTemplateFunctionGetter(tplTwitchFollowAge), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Looks up when `from` followed `to` and returns the duration between then and now",
|
||||||
|
Syntax: "followAge <from> <to>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ followAge "tezrian" "luziferus" }}`,
|
||||||
|
FakedOutput: "15004h14m59.116620989s",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("followDate", plugins.GenericTemplateFunctionGetter(tplTwitchFollowDate), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Looks up when `from` followed `to`",
|
||||||
|
Syntax: "followDate <from> <to>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ followDate "tezrian" "luziferus" }}`,
|
||||||
|
FakedOutput: "2021-04-10 16:07:07 +0000 UTC",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("lastPoll", plugins.GenericTemplateFunctionGetter(tplTwitchLastPoll), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Gets the last (currently running or archived) poll for the given channel (the channel must have given extended permission for poll access!)",
|
||||||
|
Syntax: "lastPoll <channel>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `Last Poll: {{ (lastPoll .channel).Title }}`,
|
||||||
|
FakedOutput: "Last Poll: Und wie siehts im Template aus?",
|
||||||
|
},
|
||||||
|
Remarks: "See schema of returned object in [`pkg/twitch/polls.go#L13`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/polls.go#L13)",
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("profileImage", plugins.GenericTemplateFunctionGetter(tplTwitchProfileImage), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Gets the URL of the given users profile image",
|
||||||
|
Syntax: "profileImage <username>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ profileImage .username }}`,
|
||||||
|
FakedOutput: "https://static-cdn.jtvnw.net/jtv_user_pictures/[...].png",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("recentGame", plugins.GenericTemplateFunctionGetter(tplTwitchRecentGame), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the last played game name of the specified user (see shoutout example) or the `fallback` if the game could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||||
|
Syntax: "recentGame <username> [fallback]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||||
|
FakedOutput: "Metro Exodus - none",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("recentTitle", plugins.GenericTemplateFunctionGetter(tplTwitchRecentTitle), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the last stream title of the specified user or the `fallback` if the title could not be fetched. If no fallback was supplied the message will fail and not be sent.",
|
||||||
|
Syntax: "recentTitle <username> [fallback]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ recentGame "luziferus" "none" }} - {{ recentGame "thisuserdoesnotexist123" "none" }}`,
|
||||||
|
FakedOutput: "Die Oper haben wir überlebt, mal sehen was uns sonst noch alles töten möchte… - none",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tplFuncs.Register("streamUptime", plugins.GenericTemplateFunctionGetter(tplTwitchStreamUptime), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the duration the stream is online (causes an error if no current stream is found)",
|
||||||
|
Syntax: "streamUptime <username>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ formatDuration (streamUptime "luziferus") "hours" "minutes" "" }}`,
|
||||||
|
FakedOutput: "3 hours, 56 minutes",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func tplTwitchDisplayName(username string, v ...string) (string, error) {
|
func tplTwitchDisplayName(username string, v ...string) (string, error) {
|
||||||
|
|
|
@ -131,11 +131,25 @@ func Register(args plugins.RegistrationArguments) error {
|
||||||
|
|
||||||
return strings.Join([]string{channel, name}, ":"), nil
|
return strings.Join([]string{channel, name}, ":"), nil
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Wraps the counter name into a channel specific counter name including the channel name",
|
||||||
|
Syntax: "channelCounter <counter name>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ channelCounter "test" }}`,
|
||||||
|
ExpectedOutput: "#example:test",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
args.RegisterTemplateFunction("counterValue", plugins.GenericTemplateFunctionGetter(func(name string, _ ...string) (int64, error) {
|
args.RegisterTemplateFunction("counterValue", plugins.GenericTemplateFunctionGetter(func(name string, _ ...string) (int64, error) {
|
||||||
return GetCounterValue(db, name)
|
return GetCounterValue(db, name)
|
||||||
}))
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the current value of the counter which identifier was supplied",
|
||||||
|
Syntax: "counterValue <counter name>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ counterValue (list .channel "test" | join ":") }}`,
|
||||||
|
FakedOutput: "5",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
args.RegisterTemplateFunction("counterValueAdd", plugins.GenericTemplateFunctionGetter(func(name string, val ...int64) (int64, error) {
|
args.RegisterTemplateFunction("counterValueAdd", plugins.GenericTemplateFunctionGetter(func(name string, val ...int64) (int64, error) {
|
||||||
var mod int64 = 1
|
var mod int64 = 1
|
||||||
|
@ -148,7 +162,14 @@ func Register(args plugins.RegistrationArguments) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCounterValue(db, name)
|
return GetCounterValue(db, name)
|
||||||
}))
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Adds the given value (or 1 if no value) to the counter and returns its new value",
|
||||||
|
Syntax: "counterValueAdd <counter name> [increase=1]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ counterValueAdd "myCounter" }} {{ counterValueAdd "myCounter" 5 }}`,
|
||||||
|
FakedOutput: "1 6",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,13 @@ func Register(args plugins.RegistrationArguments) error {
|
||||||
return func() (int, error) {
|
return func() (int, error) {
|
||||||
return GetMaxQuoteIdx(db, plugins.DeriveChannel(m, nil))
|
return GetMaxQuoteIdx(db, plugins.DeriveChannel(m, nil))
|
||||||
}
|
}
|
||||||
|
}, plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Gets the last quote index in the quote database for the current channel",
|
||||||
|
Syntax: "lastQuoteIndex",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `Last Quote: #{{ lastQuoteIndex }}`,
|
||||||
|
FakedOutput: "Last Quote: #32",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -20,6 +20,7 @@ var (
|
||||||
ptrStringEmpty = func(s string) *string { return &s }("")
|
ptrStringEmpty = func(s string) *string { return &s }("")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:funlen // Function contains only documentation registration
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
db = args.GetDatabaseConnector()
|
db = args.GetDatabaseConnector()
|
||||||
if err := db.DB().AutoMigrate(&variable{}); err != nil {
|
if err := db.DB().AutoMigrate(&variable{}); err != nil {
|
||||||
|
@ -116,7 +117,14 @@ func Register(args plugins.RegistrationArguments) error {
|
||||||
return defVal[0], nil
|
return defVal[0], nil
|
||||||
}
|
}
|
||||||
return value, nil
|
return value, nil
|
||||||
}))
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the variable value or default in case it is empty",
|
||||||
|
Syntax: "variable <name> [default]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ variable "foo" "fallback" }} - {{ variable "unsetvar" "fallback" }}`,
|
||||||
|
FakedOutput: "test - fallback",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,25 @@ package api
|
||||||
import "github.com/Luzifer/twitch-bot/v3/plugins"
|
import "github.com/Luzifer/twitch-bot/v3/plugins"
|
||||||
|
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
args.RegisterTemplateFunction("jsonAPI", plugins.GenericTemplateFunctionGetter(jsonAPI))
|
args.RegisterTemplateFunction("jsonAPI", plugins.GenericTemplateFunctionGetter(jsonAPI), plugins.TemplateFuncDocumentation{
|
||||||
args.RegisterTemplateFunction("textAPI", plugins.GenericTemplateFunctionGetter(textAPI))
|
Description: "Fetches remote URL and applies jq-like query to it returning the result as string. (Remote API needs to return status 200 within 5 seconds.)",
|
||||||
|
Syntax: "jsonAPI <url> <jq-like path> [fallback]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ jsonAPI "https://api.github.com/repos/Luzifer/twitch-bot" ".owner.login" }}`,
|
||||||
|
FakedOutput: "Luzifer",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction("textAPI", plugins.GenericTemplateFunctionGetter(textAPI), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Fetches remote URL and returns the result as string. (Remote API needs to return status 200 within 5 seconds.)",
|
||||||
|
Syntax: "textAPI <url> [fallback]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
MatchMessage: "!weather (.*)",
|
||||||
|
MessageContent: "!weather Hamburg",
|
||||||
|
Template: `{{ textAPI (printf "https://api.scorpstuff.com/weather.php?units=metric&city=%s" (urlquery (group 1))) }}`,
|
||||||
|
FakedOutput: "Weather for Hamburg, DE: Few clouds with a temperature of 22 C (71.6 F). [...]",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
args.RegisterTemplateFunction("pow", plugins.GenericTemplateFunctionGetter(math.Pow))
|
args.RegisterTemplateFunction("pow", plugins.GenericTemplateFunctionGetter(math.Pow), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns float from calculation: `float1 ** float2`",
|
||||||
|
Syntax: "pow <float1> <float2>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ printf "%.0f" (pow 10 4) }}`,
|
||||||
|
ExpectedOutput: "10000",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
args.RegisterTemplateFunction("randomString", plugins.GenericTemplateFunctionGetter(randomString))
|
args.RegisterTemplateFunction("randomString", plugins.GenericTemplateFunctionGetter(randomString), plugins.TemplateFuncDocumentation{
|
||||||
args.RegisterTemplateFunction("seededRandom", plugins.GenericTemplateFunctionGetter(stableRandomFromSeed))
|
Description: "Randomly picks a string from a list of strings",
|
||||||
|
Syntax: "randomString <string> [...string]",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ randomString "a" "b" "c" "d" }}`,
|
||||||
|
FakedOutput: "a",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction("seededRandom", plugins.GenericTemplateFunctionGetter(stableRandomFromSeed), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns a float value stable for the given seed",
|
||||||
|
Syntax: "seededRandom <string-seed>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
args.RegisterTemplateFunction("inList", plugins.GenericTemplateFunctionGetter(str.StringInSlice))
|
args.RegisterTemplateFunction("inList", plugins.GenericTemplateFunctionGetter(func(search string, list ...string) bool {
|
||||||
|
return str.StringInSlice(search, list)
|
||||||
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Tests whether a string is in a given list of strings (for conditional templates).",
|
||||||
|
Syntax: "inList <search> <...string>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
MatchMessage: "!command (.*)",
|
||||||
|
MessageContent: "!command foo",
|
||||||
|
Template: `{{ inList (group 1) "foo" "bar" }}`,
|
||||||
|
ExpectedOutput: "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(args plugins.RegistrationArguments) error {
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
args.RegisterTemplateFunction("b64urlenc", plugins.GenericTemplateFunctionGetter(base64URLEncode))
|
args.RegisterTemplateFunction("b64urlenc", plugins.GenericTemplateFunctionGetter(base64URLEncode), plugins.TemplateFuncDocumentation{
|
||||||
args.RegisterTemplateFunction("b64urldec", plugins.GenericTemplateFunctionGetter(base64URLDecode))
|
Description: "Encodes the input using base64 URL-encoding (like `b64enc` but using `URLEncoding` instead of `StdEncoding`)",
|
||||||
|
Syntax: "b64urlenc <input>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ b64urlenc "mystring" }}`,
|
||||||
|
ExpectedOutput: "bXlzdHJpbmc=",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction("b64urldec", plugins.GenericTemplateFunctionGetter(base64URLDecode), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Decodes the input using base64 URL-encoding (like `b64dec` but using `URLEncoding` instead of `StdEncoding`)",
|
||||||
|
Syntax: "b64urldec <input>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ b64urldec "bXlzdHJpbmc=" }}`,
|
||||||
|
ExpectedOutput: "mystring",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,24 @@ func Register(args plugins.RegistrationArguments) error {
|
||||||
permCheckFn = args.HasPermissionForChannel
|
permCheckFn = args.HasPermissionForChannel
|
||||||
tcGetter = args.GetTwitchClientForChannel
|
tcGetter = args.GetTwitchClientForChannel
|
||||||
|
|
||||||
args.RegisterTemplateFunction("subCount", plugins.GenericTemplateFunctionGetter(subCount))
|
args.RegisterTemplateFunction("subCount", plugins.GenericTemplateFunctionGetter(subCount), plugins.TemplateFuncDocumentation{
|
||||||
args.RegisterTemplateFunction("subPoints", plugins.GenericTemplateFunctionGetter(subPoints))
|
Description: "Returns the number of subscribers (accounts) currently subscribed to the given channel",
|
||||||
|
Syntax: "subCount <channel>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ subCount "luziferus" }}`,
|
||||||
|
FakedOutput: "26",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction("subPoints", plugins.GenericTemplateFunctionGetter(subPoints), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Returns the number of sub-points currently given through the T1 / T2 / T3 subscriptions to the given channel",
|
||||||
|
Syntax: "subPoints <channel>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ subPoints "luziferus" }}`,
|
||||||
|
FakedOutput: "26",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
main_test.go
Normal file
37
main_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/internal/service/timer"
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if db, err = database.New("sqlite", "file::memory:?cache=shared", "encpass"); err != nil {
|
||||||
|
log.WithError(err).Fatal("opening storage backend")
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessService, err = access.New(db); err != nil {
|
||||||
|
log.WithError(err).Fatal("applying access migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
cronService = cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
if timerService, err = timer.New(db, cronService); err != nil {
|
||||||
|
log.WithError(err).Fatal("applying timer migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = initCorePlugins(); err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to load core plugins")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
TemplateFuncGetter func(*irc.Message, *Rule, *FieldCollection) interface{}
|
TemplateFuncGetter func(*irc.Message, *Rule, *FieldCollection) interface{}
|
||||||
TemplateFuncRegister func(name string, fg TemplateFuncGetter)
|
TemplateFuncRegister func(name string, fg TemplateFuncGetter, doc ...TemplateFuncDocumentation)
|
||||||
|
|
||||||
TemplateValidatorFunc func(raw string) error
|
TemplateValidatorFunc func(raw string) error
|
||||||
|
|
||||||
|
|
19
plugins/tplfuncdoc.go
Normal file
19
plugins/tplfuncdoc.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
type (
|
||||||
|
TemplateFuncDocumentation struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Syntax string
|
||||||
|
Example *TemplateFuncDocumentationExample
|
||||||
|
Remarks string
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplateFuncDocumentationExample struct {
|
||||||
|
MatchMessage string
|
||||||
|
MessageContent string
|
||||||
|
Template string
|
||||||
|
ExpectedOutput string
|
||||||
|
FakedOutput string
|
||||||
|
}
|
||||||
|
)
|
94
tplDocs.go
Normal file
94
tplDocs.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-irc/irc"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed tplDocs.tpl
|
||||||
|
var tplDocsTemplate string
|
||||||
|
|
||||||
|
func generateTplDocs() ([]byte, error) {
|
||||||
|
tpl, err := template.New("tplDocs").Funcs(map[string]any{
|
||||||
|
"renderExample": generateTplDocsRender,
|
||||||
|
}).Parse(tplDocsTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "parsing tplDocs template")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(tplFuncs.docs, func(i, j int) bool { return tplFuncs.docs[i].Name < tplFuncs.docs[j].Name })
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := tpl.Execute(buf, struct {
|
||||||
|
Funcs []plugins.TemplateFuncDocumentation
|
||||||
|
}{
|
||||||
|
Funcs: tplFuncs.docs,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "rendering tplDocs template")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTplDocsRender(e *plugins.TemplateFuncDocumentationExample) (string, error) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logrus.WithError(err.(error)).Fatalf("%s", debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
content := e.MessageContent
|
||||||
|
if content == "" {
|
||||||
|
content = "Hello World"
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &irc.Message{
|
||||||
|
Command: "PRIVMSG",
|
||||||
|
Params: []string{
|
||||||
|
"#example",
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
Prefix: &irc.Prefix{
|
||||||
|
Name: "exampleuser",
|
||||||
|
User: "exampleuser",
|
||||||
|
Host: "exampleuser.tmi.twitch.tv",
|
||||||
|
},
|
||||||
|
Tags: map[string]irc.TagValue{
|
||||||
|
"badge-info": "subscriber/26",
|
||||||
|
"badges": "moderator/1,subscriber/24",
|
||||||
|
"color": "#8A2BE2",
|
||||||
|
"display-name": "ExampleUser",
|
||||||
|
"emotes": "",
|
||||||
|
"first-msg": "0",
|
||||||
|
"flags": "",
|
||||||
|
"id": "d3167f1f-5a0c-4d78-ba68-1a6c0018d284",
|
||||||
|
"mod": "1",
|
||||||
|
"returning-chatter": "0",
|
||||||
|
"room-id": "123456",
|
||||||
|
"subscriber": "1",
|
||||||
|
"tmi-sent-ts": "1679582970403",
|
||||||
|
"turbo": "0",
|
||||||
|
"user-id": "987654",
|
||||||
|
"user-type": "mod",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &plugins.Rule{}
|
||||||
|
if e.MatchMessage != "" {
|
||||||
|
rule.MatchMessage = &e.MatchMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatMessage(e.Template, msg, rule, plugins.FieldCollectionFromData(map[string]any{
|
||||||
|
"testDuration": 5*time.Hour + 33*time.Minute + 12*time.Second,
|
||||||
|
}))
|
||||||
|
}
|
79
tplDocs.tpl
Normal file
79
tplDocs.tpl
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
title: "Templating"
|
||||||
|
---
|
||||||
|
|
||||||
|
{{"{{< lead >}}"}}
|
||||||
|
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.
|
||||||
|
{{"{{< /lead >}}"}}
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
Within templates following functions can be used:
|
||||||
|
|
||||||
|
- built-in functions in `text/template` engine
|
||||||
|
- functions from [sprig](https://masterminds.github.io/sprig/) function collection
|
||||||
|
- functions mentioned below
|
||||||
|
|
||||||
|
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 (Rendered during docs generation)
|
||||||
|
* Output from the template (Static output, template not rendered)
|
||||||
|
```
|
||||||
|
|
||||||
|
{{ range .Funcs -}}
|
||||||
|
### `{{ .Name }}`
|
||||||
|
|
||||||
|
{{ .Description }}
|
||||||
|
|
||||||
|
Syntax: `{{ .Syntax }}`
|
||||||
|
|
||||||
|
{{- if .Example }}
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{- if .Example.MatchMessage }}
|
||||||
|
! {{ .Example.MatchMessage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Example.MessageContent }}
|
||||||
|
> {{ .Example.MessageContent }}
|
||||||
|
{{- end }}
|
||||||
|
# {{ .Example.Template }}
|
||||||
|
{{- if .Example.FakedOutput }}
|
||||||
|
* {{ .Example.FakedOutput }}
|
||||||
|
{{- else }}
|
||||||
|
< {{ renderExample .Example }}
|
||||||
|
{{- end }}
|
||||||
|
```
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ if .Remarks -}}
|
||||||
|
{{ .Remarks }}
|
||||||
|
|
||||||
|
{{ end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
## Upgrade from `v2.x` to `v3.x`
|
||||||
|
|
||||||
|
When adding [sprig](https://masterminds.github.io/sprig/) function collection some functions collided and needed replacement. You need to adapt your templates accordingly:
|
||||||
|
|
||||||
|
- Math functions (`add`, `div`, `mod`, `mul`, `multiply`, `sub`) were replaced with their sprig-equivalent and are now working with integers instead of floats. If you need them to continue to work with floats you need to use their [float-variants](https://masterminds.github.io/sprig/mathf.html).
|
||||||
|
- `now` does no longer format the current date as a string but return the current date. You need to replace this: `now "2006-01-02"` becomes `now | date "2006-01-02"`.
|
||||||
|
- `concat` is now used to concat arrays. To join strings you will need to modify your code: `concat ":" "string1" "string2"` becomes `lists "string1" "string2" | join ":"`.
|
||||||
|
- `toLower` / `toUpper` need to be replaced with their sprig equivalent `lower` and `upper`.
|
||||||
|
|
||||||
|
{{ if false }}<!-- vim: set ft=markdown: -->{{ end }}
|
25
tplDocs_test.go
Normal file
25
tplDocs_test.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplateFuncDocs(t *testing.T) {
|
||||||
|
for _, fd := range tplFuncs.docs {
|
||||||
|
t.Run(fd.Name, func(t *testing.T) {
|
||||||
|
if fd.Example == nil {
|
||||||
|
t.Skip("no example present")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fd.Example.ExpectedOutput == "" {
|
||||||
|
t.Skip("no expected output present")
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := generateTplDocsRender(fd.Example)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, fd.Example.ExpectedOutput, out)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue