1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: add rudimentary template checking on chat commands

This commit is contained in:
Ash Keel 2023-05-19 15:07:32 +02:00
parent d37c7ff1b7
commit 4ca7b4c6be
11 changed files with 115 additions and 19 deletions

View file

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Custom chat commands can now be sent as replies, whispers and announcements. Due to some API shenanigans yet to be solved, the latter two will always be sent from your main account, not the bot account (if they are different)
### Fixed
- Fixed some minor bugs regarding backup file sorting and migration procedures
## [3.2.1] - 2023-05-17
### Fixed

12
app.go
View file

@ -290,6 +290,18 @@ func (a *App) GetAppVersion() VersionInfo {
}
}
func (a *App) TestTemplate(message string, data any) error {
tpl, err := a.twitchManager.Client().Bot.MakeTemplate(message)
if err != nil {
return err
}
return tpl.Execute(io.Discard, data)
}
func (a *App) TestCommandTemplate(message string) error {
return a.TestTemplate(message, twitch.TestMessageData)
}
func (a *App) interactiveAuth(client kv.Client, message map[string]any) bool {
callbackID := fmt.Sprintf("auth-callback-%d", client.UID())
authResult := make(chan bool)

View file

@ -131,7 +131,8 @@
},
"remove-command-title": "Remove command {{name}}?",
"no-commands": "Chatbot has no commands configured",
"command-already-in-use": "Command name already in use"
"command-already-in-use": "Command name already in use",
"command-invalid-format": "The response template contains errors"
},
"bottimers": {
"title": "Bot timers",

View file

@ -118,7 +118,8 @@
"chat": "Canale",
"reply": "Risposta",
"whisper": "Chat privata"
}
},
"command-invalid-format": "Il messaggio contiene errori"
},
"bottimers": {
"add-button": "Nuovo timer",

View file

@ -1,5 +1,5 @@
import { PlusIcon } from '@radix-ui/react-icons';
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useModule } from '~/lib/react';
import { useAppDispatch } from '~/store';
@ -35,6 +35,7 @@ import {
TextBlock,
} from '../theme';
import { Alert, AlertTrigger } from '../theme/alert';
import { TestCommandTemplate } from '@wailsapp/go/main/App';
const CommandList = styled('div', { marginTop: '1rem' });
const CommandItemContainer = styled('article', {
@ -227,9 +228,11 @@ function CommandDialog({
item?.response_type ?? 'chat',
);
const [response, setResponse] = useState(item?.response ?? '');
const responseRef = useRef<HTMLTextAreaElement>(null);
const [accessLevel, setAccessLevel] = useState(
item?.access_level ?? 'everyone',
);
const [responseError, setResponseError] = useState<string | null>(null);
const { t } = useTranslation();
const replyTypes: ReplyType[] = ['chat', 'reply', 'whisper', 'announce'];
@ -239,20 +242,29 @@ function CommandDialog({
closeButton={true}
>
<form
onSubmit={(e) => {
onSubmit={async (e) => {
if (!(e.target as HTMLFormElement).checkValidity()) {
return;
}
e.preventDefault();
if (onSubmit) {
onSubmit(commandName, {
...item,
description,
response,
response_type: responseType,
access_level: accessLevel,
});
try {
await TestCommandTemplate(response);
if (onSubmit) {
onSubmit(commandName, {
...item,
description,
response,
response_type: responseType,
access_level: accessLevel,
});
}
} catch (e) {
setResponseError(e);
responseRef.current?.setCustomValidity(
t('pages.botcommands.command-invalid-format'),
);
}
}}
>
<Field spacing="narrow" size="fullWidth">
@ -309,12 +321,21 @@ function CommandDialog({
<Textarea
value={response}
required={true}
onChange={(e) => setResponse(e.target.value)}
onChange={(e) => {
responseRef.current?.setCustomValidity('');
setResponse(e.target.value)
}}
id="command-response"
ref={responseRef}
placeholder={t('pages.botcommands.command-response-placeholder')}
>
{item?.response}
</Textarea>
{responseError && (
<FieldNote css={{
color: '$red10'
}}>{responseError}</FieldNote>
)}
</Field>
<Field spacing="narrow" size="fullWidth">
<Label htmlFor="command-acl">

View file

@ -27,3 +27,7 @@ export function IsServerReady():Promise<boolean>;
export function RestoreBackup(arg1:string):Promise<void>;
export function SendCrashReport(arg1:string,arg2:string):Promise<string>;
export function TestCommandTemplate(arg1:string):Promise<void>;
export function TestTemplate(arg1:string,arg2:any):Promise<void>;

View file

@ -49,3 +49,11 @@ export function RestoreBackup(arg1) {
export function SendCrashReport(arg1, arg2) {
return window['go']['main']['App']['SendCrashReport'](arg1, arg2);
}
export function TestCommandTemplate(arg1) {
return window['go']['main']['App']['TestCommandTemplate'](arg1);
}
export function TestTemplate(arg1, arg2) {
return window['go']['main']['App']['TestTemplate'](arg1, arg2);
}

View file

@ -7,7 +7,6 @@ import (
"text/template"
"time"
"github.com/Masterminds/sprig/v3"
jsoniter "github.com/json-iterator/go"
"github.com/nicklaw5/helix/v2"
"go.uber.org/zap"
@ -474,7 +473,7 @@ func (m *BotAlertsModule) compileTemplates() {
}
func (m *BotAlertsModule) addTemplate(templateList templateCache, message string) {
tpl, err := template.New("").Funcs(m.bot.customFunctions).Funcs(sprig.TxtFuncMap()).Parse(message)
tpl, err := m.bot.MakeTemplate(message)
if err != nil {
m.bot.logger.Error("Error compiling alert template", zap.Error(err))
return

View file

@ -7,7 +7,6 @@ import (
"time"
"git.sr.ht/~hamcha/containers/sync"
"github.com/Masterminds/sprig/v3"
irc "github.com/gempir/go-twitch-irc/v4"
"go.uber.org/zap"
@ -251,7 +250,7 @@ func (b *Bot) handleWriteMessageRPC(value string) {
func (b *Bot) updateTemplates() error {
b.customTemplates.Set(make(map[string]*template.Template))
for cmd, tmpl := range b.customCommands.Copy() {
tpl, err := template.New("").Funcs(sprig.TxtFuncMap()).Funcs(b.customFunctions).Parse(tmpl.Response)
tpl, err := b.MakeTemplate(tmpl.Response)
if err != nil {
return err
}

View file

@ -24,7 +24,7 @@ type Manager struct {
}
func NewManager(db *database.LocalDBClient, server *http.Server, logger *zap.Logger) (*Manager, error) {
// Get Twitch Config
// Get Twitch config
var config Config
if err := db.GetJSON(ConfigKey, &config); err != nil {
if !errors.Is(err, database.ErrEmptyKey) {
@ -33,7 +33,7 @@ func NewManager(db *database.LocalDBClient, server *http.Server, logger *zap.Log
config.Enabled = false
}
// Get Twitch bot Config
// Get Twitch bot config
var botConfig BotConfig
if err := db.GetJSON(BotConfigKey, &botConfig); err != nil {
if !errors.Is(err, database.ErrEmptyKey) {

View file

@ -2,10 +2,12 @@ package twitch
import (
"bytes"
"github.com/Masterminds/sprig/v3"
"math/rand"
"strconv"
"strings"
"text/template"
"time"
irc "github.com/gempir/go-twitch-irc/v4"
"github.com/nicklaw5/helix/v2"
@ -94,6 +96,51 @@ func cmdCustom(bot *Bot, cmd string, data BotCustomCommand, message irc.PrivateM
}
}
func (b *Bot) MakeTemplate(message string) (*template.Template, error) {
return template.New("").Funcs(sprig.TxtFuncMap()).Funcs(b.customFunctions).Parse(message)
}
var TestMessageData = irc.PrivateMessage{
User: irc.User{
ID: "603448316",
Name: "ashkeelvt",
DisplayName: "AshKeelVT",
Color: "#EC2B87",
Badges: map[string]int{
"subscriber": 0,
"moments": 1,
"broadcaster": 1,
},
},
Type: 1,
Tags: map[string]string{
"emotes": "",
"first-msg": "0",
"id": "e6b80ab3-d068-4226-83b2-a991da9c0cc3",
"turbo": "0",
"user-id": "603448316",
"badges": "broadcaster/1,subscriber/0,moments/1",
"color": "#EC2B87",
"user-type": "",
"room-id": "603448316",
"tmi-sent-ts": "1684345559394",
"flags": "",
"mod": "0",
"returning-chatter": "0",
"badge-info": "subscriber/21",
"display-name": "AshKeelVT",
"subscriber": "1",
},
Message: "!test",
Channel: "ashkeelvt",
RoomID: "603448316",
ID: "e6b80ab3-d068-4226-83b2-a991da9c0cc3",
Time: time.Now(),
Emotes: nil,
Bits: 0,
Action: false,
}
func (b *Bot) setupFunctions() {
b.customFunctions = template.FuncMap{
"user": func(message irc.PrivateMessage) string {