diff --git a/docs/content/configuration/templating.md b/docs/content/configuration/templating.md index 4ac7483..9bb4980 100644 --- a/docs/content/configuration/templating.md +++ b/docs/content/configuration/templating.md @@ -100,6 +100,32 @@ Example: < #example:test ``` +### `counterRank` + +Returns the rank of the given counter and the total number of counters in given counter prefix + +Syntax: `counterRank ` + +Example: + +``` +# {{ $cr := counterRank (list .channel "test" "" | join ":") (list .channel "test" "foo" | join ":") }}{{ $cr.Rank }}/{{ $cr.Count }} +* 2/6 +``` + +### `counterTopList` + +Returns the top n counters for the given prefix as objects with Name and Value fields + +Syntax: `counterTopList ` + +Example: + +``` +# {{ range (counterTopList (list .channel "test" "" | join ":") 3) }}{{ .Name }}: {{ .Value }} - {{ end }} +* #example:test:foo: 5 - #example:test:bar: 4 - +``` + ### `counterValue` Returns the current value of the counter which identifier was supplied @@ -389,7 +415,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: 11% +< Your int this hour: 43% ``` ### `streamUptime` diff --git a/internal/actors/counter/actor.go b/internal/actors/counter/actor.go index 5c934b3..c3682e3 100644 --- a/internal/actors/counter/actor.go +++ b/internal/actors/counter/actor.go @@ -24,7 +24,7 @@ var ( //nolint:funlen // This function is a few lines too long but only contains definitions func Register(args plugins.RegistrationArguments) error { db = args.GetDatabaseConnector() - if err := db.DB().AutoMigrate(&counter{}); err != nil { + if err := db.DB().AutoMigrate(&Counter{}); err != nil { return errors.Wrap(err, "applying schema migration") } @@ -140,6 +140,29 @@ func Register(args plugins.RegistrationArguments) error { }, }) + args.RegisterTemplateFunction("counterRank", plugins.GenericTemplateFunctionGetter(func(prefix, name string) (res struct{ Rank, Count int64 }, err error) { + res.Rank, res.Count, err = getCounterRank(db, prefix, name) + return res, errors.Wrap(err, "getting counter rank") + }), plugins.TemplateFuncDocumentation{ + Description: "Returns the rank of the given counter and the total number of counters in given counter prefix", + Syntax: `counterRank `, + Example: &plugins.TemplateFuncDocumentationExample{ + Template: `{{ $cr := counterRank (list .channel "test" "" | join ":") (list .channel "test" "foo" | join ":") }}{{ $cr.Rank }}/{{ $cr.Count }}`, + FakedOutput: "2/6", + }, + }) + + args.RegisterTemplateFunction("counterTopList", plugins.GenericTemplateFunctionGetter(func(prefix string, n int) ([]Counter, error) { + return getCounterTopList(db, prefix, n) + }), plugins.TemplateFuncDocumentation{ + Description: "Returns the top n counters for the given prefix as objects with Name and Value fields", + Syntax: `counterTopList `, + Example: &plugins.TemplateFuncDocumentationExample{ + Template: `{{ range (counterTopList (list .channel "test" "" | join ":") 3) }}{{ .Name }}: {{ .Value }} - {{ end }}`, + FakedOutput: "#example:test:foo: 5 - #example:test:bar: 4 - ", + }, + }) + args.RegisterTemplateFunction("counterValue", plugins.GenericTemplateFunctionGetter(func(name string, _ ...string) (int64, error) { return GetCounterValue(db, name) }), plugins.TemplateFuncDocumentation{ diff --git a/internal/actors/counter/database.go b/internal/actors/counter/database.go index 51923b6..1793ca5 100644 --- a/internal/actors/counter/database.go +++ b/internal/actors/counter/database.go @@ -9,14 +9,14 @@ import ( ) type ( - counter struct { + Counter struct { Name string `gorm:"primaryKey"` Value int64 } ) func GetCounterValue(db database.Connector, counterName string) (int64, error) { - var c counter + var c Counter err := db.DB().First(&c, "name = ?", counterName).Error switch { @@ -45,7 +45,40 @@ func UpdateCounter(db database.Connector, counterName string, value int64, absol db.DB().Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "name"}}, DoUpdates: clause.AssignmentColumns([]string{"value"}), - }).Create(counter{Name: counterName, Value: value}).Error, + }).Create(Counter{Name: counterName, Value: value}).Error, "storing counter value", ) } + +func getCounterRank(db database.Connector, prefix, name string) (rank, count int64, err error) { + var cc []Counter + + err = db.DB(). + Order("value DESC"). + Find(&cc, "name LIKE ?", prefix+"%"). + Error + if err != nil { + return 0, 0, errors.Wrap(err, "querying counters") + } + + for i, c := range cc { + count++ + if c.Name == name { + rank = int64(i + 1) + } + } + + return rank, count, nil +} + +func getCounterTopList(db database.Connector, prefix string, n int) ([]Counter, error) { + var cc []Counter + + err := db.DB(). + Order("value DESC"). + Limit(n). + Find(&cc, "name LIKE ?", prefix+"%"). + Error + + return cc, errors.Wrap(err, "querying counters") +} diff --git a/internal/actors/counter/database_test.go b/internal/actors/counter/database_test.go index 6c2cac2..55b405a 100644 --- a/internal/actors/counter/database_test.go +++ b/internal/actors/counter/database_test.go @@ -1,16 +1,18 @@ package counter import ( + "fmt" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/Luzifer/twitch-bot/v3/pkg/database" ) func TestCounterStoreLoop(t *testing.T) { dbc := database.GetTestDatabase(t) - dbc.DB().AutoMigrate(&counter{}) + dbc.DB().AutoMigrate(&Counter{}) counterName := "mytestcounter" @@ -28,3 +30,34 @@ func TestCounterStoreLoop(t *testing.T) { assert.NoError(t, err, "reading existent counter") assert.Equal(t, int64(6), v, "expecting counter value on existing counter") } + +func TestCounterTopListAndRank(t *testing.T) { + dbc := database.GetTestDatabase(t) + dbc.DB().AutoMigrate(&Counter{}) + + counterTemplate := `#example:test:%v` + for i := 0; i < 6; i++ { + require.NoError( + t, + UpdateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true), + "inserting counter %d", i, + ) + } + + cc, err := getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3) + require.NoError(t, err) + assert.Len(t, cc, 3) + + assert.Equal(t, []Counter{ + {Name: "#example:test:5", Value: 5}, + {Name: "#example:test:4", Value: 4}, + {Name: "#example:test:3", Value: 3}, + }, cc) + + rank, count, err := getCounterRank(dbc, + fmt.Sprintf(counterTemplate, ""), + fmt.Sprintf(counterTemplate, 4)) + require.NoError(t, err) + assert.Equal(t, int64(6), count) + assert.Equal(t, int64(2), rank) +}