Merge branch 'master' into dependabot/go_modules/github.com/antchfx/xmlquery-1.3.1

This commit is contained in:
Chris Sexton 2023-10-30 13:41:40 -04:00 committed by GitHub
commit 3fe4eda22b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 336 additions and 208 deletions

View File

@ -147,6 +147,8 @@ func (b *bot) setupHTTP() {
b.router.HandleFunc("/", b.serveRoot) b.router.HandleFunc("/", b.serveRoot)
b.router.HandleFunc("/nav", b.serveNav) b.router.HandleFunc("/nav", b.serveNav)
b.router.HandleFunc("/navHTML", b.serveNavHTML)
b.router.HandleFunc("/navHTML/{currentPage}", b.serveNavHTML)
} }
func (b *bot) ListenAndServe() { func (b *bot) ListenAndServe() {

View File

@ -26,6 +26,8 @@ const (
Reply Reply
// Action any /me action // Action any /me action
Action Action
// Spoiler is for commented out messages
Spoiler
// Reaction Icon reaction if service supports it // Reaction Icon reaction if service supports it
Reaction Reaction
// Edit message ref'd new message to replace // Edit message ref'd new message to replace

View File

@ -37,6 +37,9 @@ func (mb *MockBot) GetPassword() string { return "12345" }
func (mb *MockBot) SetQuiet(bool) {} func (mb *MockBot) SetQuiet(bool) {}
func (mb *MockBot) Send(c Connector, kind Kind, args ...any) (string, error) { func (mb *MockBot) Send(c Connector, kind Kind, args ...any) (string, error) {
switch kind { switch kind {
case Spoiler:
mb.Messages = append(mb.Messages, "||"+args[1].(string)+"||")
return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil
case Message: case Message:
mb.Messages = append(mb.Messages, args[1].(string)) mb.Messages = append(mb.Messages, args[1].(string))
return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil

22
bot/nav.html Normal file
View File

@ -0,0 +1,22 @@
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/">catbase</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
{{- $currentPage := .CurrentPage -}}
{{range .Items}}
<li class="nav-item">
{{ if (eq $currentPage .Name) }}
<a class="nav-link active" aria-current="page" href="{{.URL}}">{{.Name}}</a>
{{ else }}
<a class="nav-link" href="{{.URL}}">{{.Name}}</a>
{{ end }}
</li>
{{end}}
</ul>
</div>
</div>
</nav>

View File

@ -3,8 +3,12 @@ package bot
import ( import (
"embed" "embed"
"encoding/json" "encoding/json"
"fmt"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"net/http" "net/http"
"strings" "strings"
"text/template"
) )
//go:embed *.html //go:embed *.html
@ -15,6 +19,19 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
w.Write(index) w.Write(index)
} }
func (b *bot) serveNavHTML(w http.ResponseWriter, r *http.Request) {
currentPage := chi.URLParam(r, "currentPage")
tpl := template.Must(template.ParseFS(embeddedFS, "nav.html"))
if err := tpl.Execute(w, struct {
CurrentPage string
Items []EndPoint
}{currentPage, b.GetWebNavigation()}); err != nil {
log.Error().Err(err).Msg("template error")
w.WriteHeader(500)
fmt.Fprint(w, "Error parsing nav template")
}
}
func (b *bot) serveNav(w http.ResponseWriter, r *http.Request) { func (b *bot) serveNav(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
err := enc.Encode(b.GetWebNavigation()) err := enc.Encode(b.GetWebNavigation())

View File

@ -74,6 +74,9 @@ func (d Discord) Send(kind bot.Kind, args ...any) (string, error) {
return d.sendMessage(args[0].(string), args[2].(string), false, args...) return d.sendMessage(args[0].(string), args[2].(string), false, args...)
case bot.Message: case bot.Message:
return d.sendMessage(args[0].(string), args[1].(string), false, args...) return d.sendMessage(args[0].(string), args[1].(string), false, args...)
case bot.Spoiler:
outgoing := "||" + args[1].(string) + "||"
return d.sendMessage(args[0].(string), outgoing, false, args...)
case bot.Action: case bot.Action:
return d.sendMessage(args[0].(string), args[1].(string), true, args...) return d.sendMessage(args[0].(string), args[1].(string), true, args...)
case bot.Edit: case bot.Edit:
@ -151,7 +154,27 @@ func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...a
Interface("data", data). Interface("data", data).
Msg("sending message") Msg("sending message")
st, err := d.client.ChannelMessageSendComplex(channel, data) maxLen := 2000
chunkSize := maxLen - 100
var st *discordgo.Message
var err error
if len(data.Content) > maxLen {
tmp := data.Content
data.Content = tmp[:chunkSize]
st, err = d.client.ChannelMessageSendComplex(channel, data)
if err != nil {
return "", err
}
for i := chunkSize; i < len(data.Content); i += chunkSize {
data := &discordgo.MessageSend{Content: tmp[i : i+chunkSize]}
st, err = d.client.ChannelMessageSendComplex(channel, data)
if err != nil {
break
}
}
} else {
st, err = d.client.ChannelMessageSendComplex(channel, data)
}
//st, err := d.client.ChannelMessageSend(channel, message) //st, err := d.client.ChannelMessageSend(channel, message)
if err != nil { if err != nil {

View File

@ -135,7 +135,6 @@ func main() {
b.AddPlugin(roles.New(b)) b.AddPlugin(roles.New(b))
b.AddPlugin(twitch.New(b)) b.AddPlugin(twitch.New(b))
b.AddPlugin(pagecomment.New(b)) b.AddPlugin(pagecomment.New(b))
b.AddPlugin(gpt.New(b))
b.AddPlugin(secrets.New(b)) b.AddPlugin(secrets.New(b))
b.AddPlugin(mayi.New(b)) b.AddPlugin(mayi.New(b))
b.AddPlugin(giphy.New(b)) b.AddPlugin(giphy.New(b))
@ -179,8 +178,9 @@ func main() {
b.AddPlugin(cowboy.New(b)) b.AddPlugin(cowboy.New(b))
b.AddPlugin(topic.New(b)) b.AddPlugin(topic.New(b))
b.AddPlugin(talker.New(b)) b.AddPlugin(talker.New(b))
// catches anything left, will always return true
b.AddPlugin(fact.New(b)) b.AddPlugin(fact.New(b))
// catches anything left, will always return true
b.AddPlugin(gpt.New(b))
if err := client.Serve(); err != nil { if err := client.Serve(); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)

View File

@ -1,77 +1,36 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Load required Bootstrap and BootstrapVue CSS --> <meta charset="utf-8">
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/> <meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@^2/dist/bootstrap-vue.min.css"/> <title>vars</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<!-- Load polyfills to support older browsers --> <script src="https://unpkg.com/htmx.org@1.9.4"></script>
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue@^2/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@^2/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/axios/dist/axios.min.js"></script>
<meta charset="UTF-8">
<title>Vars</title>
</head> </head>
<body> <body>
<div hx-get="/navHTML/Variables" hx-trigger="load" hx-swap="outerHTML"></div>
<div id="app"> <div class="container">
<b-navbar> <table class="table-responsive table-striped">
<b-navbar-brand>Variables</b-navbar-brand> <thead>
<b-navbar-nav> <tr>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Variables'">{{ item.name }} <th>Key</th>
</b-nav-item> <th>Value</th>
</b-navbar-nav> </tr>
</b-navbar> </thead>
<b-alert <tbody>
dismissable {{range .Items}}
variant="error" <tr>
v-if="err" <td>{{.Key}}</td><td>{{.Value}}</td>
@dismissed="err = ''"> </tr>
{{ err }} {{else}}
</b-alert> <tr>
<b-container> <td colspan="2">No data</td>
<b-table </tr>
fixed {{end}}
:items="vars" </tbody>
:sort-by.sync="sortBy" </table>
:fields="fields"></b-table> </div>
</b-container> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
err: '',
nav: [],
vars: [],
sortBy: 'key',
fields: [
{key: {sortable: true}},
'value'
]
},
mounted() {
this.getData();
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
},
methods: {
getData: function () {
axios.get('/vars/api')
.then(resp => {
this.vars = resp.data;
})
.catch(err => this.err = err);
}
}
})
</script>
</body> </body>
</html> </html>

View File

@ -6,6 +6,7 @@ import (
"embed" "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
@ -166,9 +167,32 @@ func writeErr(w http.ResponseWriter, err error) {
fmt.Fprint(w, string(j)) fmt.Fprint(w, string(j))
} }
type configEntry struct {
Key string `json:"key"`
Value string `json:"value"`
}
func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
index, _ := embeddedFS.ReadFile("vars.html") tpl := template.Must(template.ParseFS(embeddedFS, "vars.html"))
w.Write(index) var configEntries []configEntry
q := `select key, value from config`
err := p.db.Select(&configEntries, q)
if err != nil {
log.Error().
Err(err).
Msg("Error getting config entries.")
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
if err := tpl.Execute(w, struct {
Items []configEntry
}{configEntries}); err != nil {
log.Error().Err(err).Msg("template error")
w.WriteHeader(500)
fmt.Fprint(w, "Error parsing template")
}
} }
func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -5,8 +5,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/velour/catbase/bot/user" "github.com/velour/catbase/bot/user"
"io/ioutil" "io"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"time" "time"
@ -29,8 +30,8 @@ func (p *CounterPlugin) registerWeb() {
subrouter.Use(httprate.LimitByIP(requests, dur)) subrouter.Use(httprate.LimitByIP(requests, dur))
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1))
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1))
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementAPI(1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1))
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementAPI(-1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1))
r.Mount("/", subrouter) r.Mount("/", subrouter)
r.HandleFunc("/api", p.handleCounterAPI) r.HandleFunc("/api", p.handleCounterAPI)
r.HandleFunc("/", p.handleCounter) r.HandleFunc("/", p.handleCounter)
@ -39,9 +40,14 @@ func (p *CounterPlugin) registerWeb() {
func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWriter, r *http.Request) { func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userName := chi.URLParam(r, "user") userName, _ := url.QueryUnescape(chi.URLParam(r, "user"))
itemName := chi.URLParam(r, "item") itemName, _ := url.QueryUnescape(chi.URLParam(r, "item"))
delta, _ := strconv.Atoi(chi.URLParam(r, "delta")) delta, err := strconv.Atoi(chi.URLParam(r, "delta"))
if err != nil || delta == 0 {
delta = direction
} else {
delta = delta * direction
}
secret, pass, ok := r.BasicAuth() secret, pass, ok := r.BasicAuth()
if !ok || !p.b.CheckPassword(secret, pass) { if !ok || !p.b.CheckPassword(secret, pass) {
@ -77,7 +83,7 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
return return
} }
body, _ := ioutil.ReadAll(r.Body) body, _ := io.ReadAll(r.Body)
postData := map[string]string{} postData := map[string]string{}
err = json.Unmarshal(body, &postData) err = json.Unmarshal(body, &postData)
personalMsg := "" personalMsg := ""
@ -85,7 +91,11 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg) personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg)
} }
chs := p.cfg.GetArray("channels", []string{p.cfg.Get("channels", "none")}) chs := p.cfg.GetMap("counter.channelItems", map[string]string{})
ch, ok := chs[itemName]
if len(chs) == 0 || !ok {
return
}
req := &bot.Request{ req := &bot.Request{
Conn: p.b.DefaultConnector(), Conn: p.b.DefaultConnector(),
Kind: bot.Message, Kind: bot.Message,
@ -93,7 +103,7 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
User: &u, User: &u,
// Noting here that we're only going to do goals in a "default" // Noting here that we're only going to do goals in a "default"
// channel even if it should send updates to others. // channel even if it should send updates to others.
Channel: chs[0], Channel: ch,
Body: fmt.Sprintf("%s += %d", itemName, delta), Body: fmt.Sprintf("%s += %d", itemName, delta),
Time: time.Now(), Time: time.Now(),
}, },
@ -102,7 +112,13 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
} }
msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s", msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s",
userName, itemName, delta, item.Count+delta*direction, p.cfg.Get("nick", "catbase"), personalMsg) userName, itemName, delta, item.Count+delta*direction, p.cfg.Get("nick", "catbase"), personalMsg)
for _, ch := range chs { if !ok {
chs := p.cfg.GetArray("counter.channels", []string{})
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
req.Msg.Channel = ch
}
} else {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
req.Msg.Channel = ch req.Msg.Channel = ch
} }
@ -112,80 +128,6 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
} }
} }
func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userName := chi.URLParam(r, "user")
itemName := chi.URLParam(r, "item")
secret, pass, ok := r.BasicAuth()
if !ok || !p.b.CheckPassword(secret, pass) {
err := fmt.Errorf("unauthorized access")
log.Error().
Err(err).
Msg("error authenticating user")
w.WriteHeader(401)
j, _ := json.Marshal(struct {
Status bool
Error string
}{false, err.Error()})
fmt.Fprint(w, string(j))
return
}
// Try to find an ID if possible
id := ""
u, err := p.b.DefaultConnector().Profile(userName)
if err == nil {
id = u.ID
}
item, err := GetUserItem(p.db, userName, id, itemName)
if err != nil {
log.Error().Err(err).Msg("error finding item")
w.WriteHeader(400)
j, _ := json.Marshal(struct {
Status bool
Error error
}{false, err})
fmt.Fprint(w, string(j))
return
}
body, _ := ioutil.ReadAll(r.Body)
postData := map[string]string{}
err = json.Unmarshal(body, &postData)
personalMsg := ""
if inputMsg, ok := postData["message"]; ok {
personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg)
}
chs := p.cfg.GetArray("channels", []string{p.cfg.Get("channels", "none")})
req := &bot.Request{
Conn: p.b.DefaultConnector(),
Kind: bot.Message,
Msg: msg.Message{
User: &u,
// Noting here that we're only going to do goals in a "default"
// channel even if it should send updates to others.
Channel: chs[0],
Body: fmt.Sprintf("%s += %d", itemName, delta),
Time: time.Now(),
},
Values: nil,
Args: nil,
}
msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s",
userName, itemName, delta, item.Count+delta, p.cfg.Get("nick", "catbase"), personalMsg)
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
req.Msg.Channel = ch
}
item.UpdateDelta(req, delta)
j, _ := json.Marshal(struct{ Status bool }{true})
fmt.Fprint(w, string(j))
}
}
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) { func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
index, _ := embeddedFS.ReadFile("index.html") index, _ := embeddedFS.ReadFile("index.html")
w.Write(index) w.Write(index)

View File

@ -90,21 +90,8 @@ func New(botInst bot.Bot) *FactoidPlugin {
// findAction simply regexes a string for the action verb // findAction simply regexes a string for the action verb
func findAction(message string) string { func findAction(message string) string {
r, err := regexp.Compile("<.+?>") r := regexp.MustCompile("<.+?>")
if err != nil { return r.FindString(message)
panic(err)
}
action := r.FindString(message)
if action == "" {
if strings.Contains(message, " is ") {
return "is"
} else if strings.Contains(message, " are ") {
return "are"
}
}
return action
} }
// learnFact assumes we have a learning situation and inserts a new fact // learnFact assumes we have a learning situation and inserts a new fact
@ -157,7 +144,6 @@ func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) {
f, err := GetSingleFact(p.db, fact) f, err := GetSingleFact(p.db, fact)
if err != nil { if err != nil {
log.Error().Err(err).Msg("GetSingleFact")
return findAlias(p.db, fact) return findAlias(p.db, fact)
} }
return true, f return true, f
@ -485,18 +471,7 @@ func (p *FactoidPlugin) register() {
return true return true
} }
notFound := p.c.GetArray("fact.notfound", []string{ return false
"I don't know.",
"NONONONO",
"((",
"*pukes*",
"NOPE! NOPE! NOPE!",
"One time, I learned how to jump rope.",
})
// We didn't find anything, panic!
p.b.Send(c, bot.Message, message.Channel, notFound[rand.Intn(len(notFound))])
return true
}}, }},
} }
p.b.RegisterTable(p, p.handlers) p.b.RegisterTable(p, p.handlers)

View File

@ -6,7 +6,7 @@ import (
) )
import "github.com/andrewstuart/openai" import "github.com/andrewstuart/openai"
var session *openai.ChatSession var session openai.ChatSession
var client *openai.Client var client *openai.Client
func (p *GPTPlugin) getClient() (*openai.Client, error) { func (p *GPTPlugin) getClient() (*openai.Client, error) {
@ -14,31 +14,37 @@ func (p *GPTPlugin) getClient() (*openai.Client, error) {
if token == "" { if token == "" {
return nil, fmt.Errorf("no GPT token given") return nil, fmt.Errorf("no GPT token given")
} }
if client == nil { return openai.NewClient(token)
return openai.NewClient(token)
}
return client, nil
} }
func (p *GPTPlugin) chatGPT(request string) (string, error) { func (p *GPTPlugin) chatGPT(request string) (string, error) {
if session == nil { if client == nil {
if err := p.setDefaultPrompt(); err != nil { if err := p.setPrompt(p.getDefaultPrompt()); err != nil {
return "", err return "", err
} }
} }
if p.chatCount > p.c.GetInt("gpt.maxchats", 10) {
p.setPrompt(p.c.Get("gpt3.lastprompt", p.getDefaultPrompt()))
p.chatCount = 0
}
p.chatCount++
return session.Complete(context.Background(), request) return session.Complete(context.Background(), request)
} }
func (p *GPTPlugin) setDefaultPrompt() error { func (p *GPTPlugin) getDefaultPrompt() string {
return p.setPrompt(p.c.Get("gpt.prompt", "")) return p.c.Get("gpt.prompt", "")
} }
func (p *GPTPlugin) setPrompt(prompt string) error { func (p *GPTPlugin) setPrompt(prompt string) error {
client, err := p.getClient() var err error
client, err = p.getClient()
if err != nil {
return err
}
session = client.NewChatSession(prompt)
err = p.c.Set("gpt3.lastprompt", prompt)
if err != nil { if err != nil {
return err return err
} }
sess := client.NewChatSession(prompt)
session = &sess
return nil return nil
} }

View File

@ -23,6 +23,8 @@ type GPTPlugin struct {
b bot.Bot b bot.Bot
c *config.Config c *config.Config
h bot.HandlerTable h bot.HandlerTable
chatCount int
} }
func New(b bot.Bot) *GPTPlugin { func New(b bot.Bot) *GPTPlugin {
@ -54,6 +56,11 @@ func (p *GPTPlugin) register() {
HelpText: "set the ChatGPT prompt", HelpText: "set the ChatGPT prompt",
Handler: p.setPromptMessage, Handler: p.setPromptMessage,
}, },
{
Kind: bot.Message, IsCmd: true,
Regex: regexp.MustCompile(`(?P<text>.*)`),
Handler: p.chatMessage,
},
} }
log.Debug().Msg("Registering GPT3 handlers") log.Debug().Msg("Registering GPT3 handlers")
p.b.RegisterTable(p, p.h) p.b.RegisterTable(p, p.h)
@ -96,7 +103,7 @@ func (p *GPTPlugin) gpt3(stem string) string {
TopP: p.c.GetFloat64("gpt3.top_p", 1), TopP: p.c.GetFloat64("gpt3.top_p", 1),
N: p.c.GetInt("gpt3.n", 1), N: p.c.GetInt("gpt3.n", 1),
Stop: p.c.GetArray("gpt3.stop", []string{"\n"}), Stop: p.c.GetArray("gpt3.stop", []string{"\n"}),
Echo: true, Echo: p.c.GetBool("gpt3.echo", false),
} }
val, err := p.mkRequest(gpt3URL, postStruct) val, err := p.mkRequest(gpt3URL, postStruct)
if err != nil { if err != nil {

View File

@ -329,7 +329,7 @@ func FindFontSize(c *config.Config, config []string, fontLocation string, w, h i
longestStr, longestW := "", 0.0 longestStr, longestW := "", 0.0
for _, s := range config { for _, s := range config {
err := m.LoadFontFace(getFont(c, fontLocation), 12) err := m.LoadFontFace(GetFont(c, fontLocation), 12)
if err != nil { if err != nil {
log.Error().Err(err).Msg("could not load font") log.Error().Err(err).Msg("could not load font")
return fontSize return fontSize
@ -343,7 +343,7 @@ func FindFontSize(c *config.Config, config []string, fontLocation string, w, h i
} }
for _, sz := range sizes { for _, sz := range sizes {
err := m.LoadFontFace(getFont(c, fontLocation), sz) // problem err := m.LoadFontFace(GetFont(c, fontLocation), sz) // problem
if err != nil { if err != nil {
log.Error().Err(err).Msg("could not load font") log.Error().Err(err).Msg("could not load font")
return fontSize return fontSize
@ -476,7 +476,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
if fontLocation == "" { if fontLocation == "" {
fontLocation = defaultFont fontLocation = defaultFont
} }
m.LoadFontFace(getFont(p.c, fontLocation), fontSize) m.LoadFontFace(GetFont(p.c, fontLocation), fontSize)
x := float64(w)*c.XPerc + float64(dx) x := float64(w)*c.XPerc + float64(dx)
y := float64(h)*c.YPerc + float64(dy) y := float64(h)*c.YPerc + float64(dy)
m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5) m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5)
@ -491,7 +491,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
if fontLocation == "" { if fontLocation == "" {
fontLocation = defaultFont fontLocation = defaultFont
} }
m.LoadFontFace(getFont(p.c, fontLocation), fontSize) m.LoadFontFace(GetFont(p.c, fontLocation), fontSize)
x := float64(w) * c.XPerc x := float64(w) * c.XPerc
y := float64(h) * c.YPerc y := float64(h) * c.YPerc
m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5) m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5)
@ -506,7 +506,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
return p.images[jsonSpec].repr, nil return p.images[jsonSpec].repr, nil
} }
func getFont(c *config.Config, name string) string { func GetFont(c *config.Config, name string) string {
location := c.Get("meme.fontLocation", "") location := c.Get("meme.fontLocation", "")
fontShortcuts := c.GetMap("meme.fontShortcuts", map[string]string{"impact": "impact.ttf"}) fontShortcuts := c.GetMap("meme.fontShortcuts", map[string]string{"impact": "impact.ttf"})
if file, ok := fontShortcuts[name]; ok { if file, ok := fontShortcuts[name]; ok {

View File

@ -120,7 +120,6 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes
channel := message.Channel channel := message.Channel
from := message.User.Name from := message.User.Name
message.Body = replaceDuration(p.when, message.Body)
parts := strings.Fields(message.Body) parts := strings.Fields(message.Body)
if len(parts) >= 5 { if len(parts) >= 5 {

View File

@ -104,7 +104,7 @@ func (p *TalkerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Messa
line = strings.Replace(line, "{nick}", nick, 1) line = strings.Replace(line, "{nick}", nick, 1)
output += line + "\n" output += line + "\n"
} }
p.bot.Send(c, bot.Message, channel, output) p.bot.Send(c, bot.Spoiler, channel, output)
return true return true
} }

View File

@ -67,7 +67,7 @@ func defaultSpec() textSpec {
} }
func (p *Tappd) overlay(img image.Image, texts []textSpec) ([]byte, error) { func (p *Tappd) overlay(img image.Image, texts []textSpec) ([]byte, error) {
font := p.c.Get("meme.font", "impact.ttf") font := meme.GetFont(p.c, p.c.Get("meme.font", "impact.ttf"))
fontSizes := []float64{48, 36, 24, 16, 12} fontSizes := []float64{48, 36, 24, 16, 12}
r := img.Bounds() r := img.Bounds()
w := r.Dx() w := r.Dx()

146
plugins/twitch/demo/main.go Normal file
View File

@ -0,0 +1,146 @@
package main
import (
"encoding/json"
"fmt"
"github.com/nicklaw5/helix"
"io"
"log"
"net/http"
)
func main() {
client, err := helix.NewClient(&helix.Options{
ClientID: "ptwtiuzl9tcrekpf3d26ey3hb7qsge",
ClientSecret: "rpa0w6qemjqp7sgrmidwi4k0kcah82",
})
if err != nil {
log.Printf("Login error: %v", err)
return
}
access, err := client.RequestAppAccessToken([]string{"user:read:email"})
if err != nil {
log.Printf("Login error: %v", err)
return
}
fmt.Printf("%+v\n", access)
// Set the access token on the client
client.SetAppAccessToken(access.Data.AccessToken)
users, err := client.GetUsers(&helix.UsersParams{
Logins: []string{"drseabass"},
})
if err != nil {
log.Printf("Error getting users: %v", err)
return
}
if users.Error != "" {
log.Printf("Users error: %s", users.Error)
return
}
log.Printf("drseabass: %+v", users.Data.Users[0])
return
resp, err := client.CreateEventSubSubscription(&helix.EventSubSubscription{
Type: helix.EventSubTypeStreamOnline,
Version: "1",
Condition: helix.EventSubCondition{
BroadcasterUserID: users.Data.Users[0].ID,
},
Transport: helix.EventSubTransport{
Method: "webhook",
Callback: "https://rathaus.chrissexton.org/live",
Secret: "s3cre7w0rd",
},
})
if err != nil {
log.Printf("Eventsub error: %v", err)
return
}
fmt.Printf("%+v\n", resp)
resp, err = client.CreateEventSubSubscription(&helix.EventSubSubscription{
Type: helix.EventSubTypeStreamOffline,
Version: "1",
Condition: helix.EventSubCondition{
BroadcasterUserID: users.Data.Users[0].ID,
},
Transport: helix.EventSubTransport{
Method: "webhook",
Callback: "https://rathaus.chrissexton.org/offline",
Secret: "s3cre7w0rd",
},
})
if err != nil {
log.Printf("Eventsub error: %v", err)
return
}
fmt.Printf("%+v\n", resp)
http.HandleFunc("/offline", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
defer r.Body.Close()
// verify that the notification came from twitch using the secret.
if !helix.VerifyEventSubNotification("s3cre7w0rd", r.Header, string(body)) {
log.Println("no valid signature on subscription")
return
} else {
log.Println("verified signature for subscription")
}
var vals map[string]any
if err = json.Unmarshal(body, &vals); err != nil {
log.Println(err)
return
}
if challenge, ok := vals["challenge"]; ok {
w.Write([]byte(challenge.(string)))
return
}
log.Printf("got offline webhook: %v\n", vals)
w.WriteHeader(200)
w.Write([]byte("ok"))
})
http.HandleFunc("/live", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
defer r.Body.Close()
// verify that the notification came from twitch using the secret.
if !helix.VerifyEventSubNotification("s3cre7w0rd", r.Header, string(body)) {
log.Println("no valid signature on subscription")
return
} else {
log.Println("verified signature for subscription")
}
var vals map[string]any
if err = json.Unmarshal(body, &vals); err != nil {
log.Println(err)
return
}
if challenge, ok := vals["challenge"]; ok {
w.Write([]byte(challenge.(string)))
return
}
log.Printf("got live webhook: %v\n", vals)
w.WriteHeader(200)
w.Write([]byte("ok"))
})
http.ListenAndServe("0.0.0.0:1337", nil)
}

1
util/stats/main.go Normal file
View File

@ -0,0 +1 @@
package stats