Compare commits

...

9 Commits

Author SHA1 Message Date
Chris Sexton 1acb78bb5a bot: disable suffix commands 2021-01-09 17:27:52 -05:00
Chris Sexton c4605cfb77 git: don't unfurl links 2021-01-09 16:58:18 -05:00
Chris Sexton bca96b6dcd meme: fix empty meme issue 2021-01-09 16:48:55 -05:00
Chris Sexton 841d4c000e meme: remove URL and format config on meme page 2021-01-09 16:30:49 -05:00
Chris Sexton 8408da48d4 meme: add config and delete
This updates the web view to provide a screen to edit any particular
config in a text area, update the image URL, and remove a meme.

fixes #330
2021-01-09 16:18:56 -05:00
Chris Sexton c61b84d21b
Merge pull request #332 from velour/memecaps
meme: add capitalization to configs, true default
2021-01-09 15:30:12 -05:00
Chris Sexton 8d52a3d1bd meme: add capitalization to configs, true default
fixes #331
2021-01-09 15:27:32 -05:00
Chris Sexton f1aa4ebeb2
Merge pull request #328 from velour/dynamic-nav
web: remove go template dependency
2021-01-09 13:53:23 -05:00
Chris Sexton bbf5b27790 web: remove go template dependency
All vue pages now request `/nav` to get a JSON array of navigation
instead of relying on the Go template to have the nav built in. This
cleans up all of the crufty `{{ "{{ thing }}" }}` that was making it
hard to wriet vue.

This also paves the way to using the new Go resource embedding so that
the pages don't need to be wrapped in Go files.
2021-01-09 13:46:28 -05:00
19 changed files with 234 additions and 115 deletions

View File

@ -61,7 +61,8 @@ type bot struct {
}
type EndPoint struct {
Name, URL string
Name string `json:"name"`
URL string `json:"url"`
}
// Variable represents a $var replacement
@ -104,6 +105,7 @@ func New(config *config.Config, connector Connector) Bot {
bot.RefreshPluginWhitelist()
http.HandleFunc("/", bot.serveRoot)
http.HandleFunc("/nav", bot.serveNav)
connector.RegisterEvent(bot.Receive)
@ -180,10 +182,6 @@ var suffixRegex *regexp.Regexp
func IsCmd(c *config.Config, message string) (bool, string) {
cmdcs := c.GetArray("CommandChar", []string{"!"})
botnick := strings.ToLower(c.Get("Nick", "bot"))
r := fmt.Sprintf(`(?i)\s*\W*\s*?%s\W*$`, botnick)
if suffixRegex == nil {
suffixRegex = regexp.MustCompile(r)
}
if botnick == "" {
log.Fatal().
Msgf(`You must run catbase -set nick -val <your bot nick>`)
@ -202,9 +200,6 @@ func IsCmd(c *config.Config, message string) (bool, string) {
if message[0] == ':' || message[0] == ',' {
message = message[1:]
}
} else if suffixRegex.MatchString(message) {
iscmd = true
message = suffixRegex.ReplaceAllString(message, "")
} else {
for _, cmdc := range cmdcs {
if strings.HasPrefix(lowerMessage, cmdc) && len(cmdc) > 0 {

View File

@ -37,6 +37,8 @@ const (
type EphemeralID string
type UnfurlLinks bool
type ImageAttachment struct {
URL string
AltTxt string

View File

@ -1,16 +1,24 @@
package bot
import (
"html/template"
"encoding/json"
"fmt"
"net/http"
"strings"
)
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
context := make(map[string]interface{})
context["Nav"] = b.GetWebNavigation()
t := template.Must(template.New("rootIndex").Parse(rootIndex))
t.Execute(w, context)
fmt.Fprint(w, rootIndex)
}
func (b *bot) serveNav(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(w)
err := enc.Encode(b.GetWebNavigation())
if err != nil {
jsonErr, _ := json.Marshal(err)
w.WriteHeader(500)
w.Write(jsonErr)
}
}
// GetWebNavigation returns a list of bootstrap-vue <b-nav-item> links
@ -54,7 +62,7 @@ var rootIndex = `
<b-navbar>
<b-navbar-brand>catbase</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
</div>
@ -64,8 +72,15 @@ var rootIndex = `
el: '#app',
data: {
err: '',
nav: {{ .Nav }},
nav: [],
},
mounted: function() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
}
})
</script>
</body>

View File

@ -281,6 +281,12 @@ func (s *SlackApp) sendMessage(channel, message string, meMessage bool, args ...
ImageURL: a.URL,
Text: a.AltTxt,
})
case bot.UnfurlLinks:
if a {
options = append(options, slack.MsgOptionEnableLinkUnfurl())
} else {
options = append(options, slack.MsgOptionDisableLinkUnfurl())
}
}
}
}

View File

@ -5,7 +5,6 @@ package admin
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"os"
"regexp"
@ -328,10 +327,8 @@ func (p *AdminPlugin) registerWeb() {
p.bot.RegisterWeb("/vars", "Variables")
}
var tpl = template.Must(template.New("factoidIndex").Parse(varIndex))
func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
fmt.Fprint(w, varIndex)
}
func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -24,7 +24,7 @@ var varIndex = `
<b-navbar>
<b-navbar-brand>Variables</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Variables'">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Variables'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
@ -32,7 +32,7 @@ var varIndex = `
variant="error"
v-if="err"
@dismissed="err = ''">
{{ "{{ err }}" }}
{{ err }}
</b-alert>
<b-container>
<b-table
@ -48,7 +48,7 @@ var varIndex = `
el: '#app',
data: {
err: '',
nav: {{ .Nav }},
nav: [],
vars: [],
sortBy: 'key',
fields: [
@ -58,6 +58,11 @@ var varIndex = `
},
mounted() {
this.getData();
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
},
methods: {
getData: function() {

View File

@ -5,7 +5,6 @@ package cli
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"time"
@ -91,10 +90,8 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
var tpl = template.Must(template.New("factoidIndex").Parse(indexHTML))
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
fmt.Fprint(w, indexHTML)
}
// Completing the Connector interface, but will not actually be a connector

View File

@ -24,14 +24,14 @@ var indexHTML = `
<b-navbar>
<b-navbar-brand>CLI</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'CLI'">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'CLI'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
dismissable
variant="error"
:show="err">
{{ "{{ err }}" }}
{{ err }}
</b-alert>
<b-container>
<b-row>
@ -80,13 +80,20 @@ var indexHTML = `
el: '#app',
data: {
err: '',
nav: {{ .Nav }},
nav: [],
answer: '',
correct: 0,
textarea: [],
user: '',
input: '',
},
mounted: function() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
},
computed: {
authenticated: function() {
if (this.user !== '')

View File

@ -4,7 +4,6 @@ import (
"database/sql"
"encoding/json"
"fmt"
"html/template"
"math/rand"
"net/http"
"regexp"
@ -626,10 +625,8 @@ func (p *CounterPlugin) registerWeb() {
p.Bot.RegisterWeb("/counter", "Counter")
}
var tpl = template.Must(template.New("factoidIndex").Parse(html))
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
fmt.Fprint(w, html)
}
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -22,14 +22,14 @@ var html = `
<b-navbar>
<b-navbar-brand>Counters</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Counter'">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Counter'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
dismissable
:show="err"
variant="error">
{{ "{{ err }}" }}
{{ err }}
</b-alert>
<b-container>
<b-row>
@ -37,14 +37,14 @@ var html = `
<b-col><b-input v-model="answer"></b-col>
</b-row>
<b-row v-for="(counter, user) in counters">
{{ "{{ user }}" }}:
{{ user }}:
<b-container>
<b-row v-for="(count, thing) in counter">
<b-col offset="1">
{{ "{{ thing }}" }}:
{{ thing }}:
</b-col>
<b-col>
{{ "{{ count }}" }}
{{ count }}
</b-col>
<b-col cols="2">
<button @click="subtract(user,thing,count)">-</button>
@ -72,12 +72,17 @@ var html = `
el: '#app',
data: {
err: '',
nav: {{ .Nav }},
nav: [],
answer: '',
correct: 0,
counters: {}
},
mounted() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
axios.get('/counter/api')
.then(resp => (this.counters = convertData(resp.data)))
.catch(err => (this.err = err));

View File

@ -842,8 +842,6 @@ func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
var tpl = template.Must(template.New("factoidIndex").Parse(factoidIndex))
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
fmt.Fprint(w, factoidIndex)
}

View File

@ -32,7 +32,7 @@ var factoidIndex = `
<b-navbar>
<b-navbar-brand>Factoids</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Factoid'">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Factoid'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
@ -40,7 +40,7 @@ var factoidIndex = `
variant="error"
v-if="err"
@dismissed="err = ''">
{{ "{{ err }}" }}
{{ err }}
</b-alert>
<b-form @submit="runQuery">
<b-container>
@ -74,7 +74,7 @@ var factoidIndex = `
router,
data: {
err: '',
nav: {{ .Nav }},
nav: [],
query: '',
results: [],
fields: [
@ -85,6 +85,11 @@ var factoidIndex = `
]
},
mounted() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
if (this.$route.query.query) {
this.query = this.$route.query.query;
this.runQuery()

View File

@ -80,5 +80,4 @@ func (p *GitPlugin) registerWeb() {
http.HandleFunc("/git/gitea/event", p.giteaEvent)
http.HandleFunc("/git/github/event", p.githubEvent)
http.HandleFunc("/git/gitlab/event", p.gitlabEvent)
p.b.RegisterWeb("/git", "Git")
}

View File

@ -44,6 +44,6 @@ func (p *GitPlugin) giteaEvent(w http.ResponseWriter, r *http.Request) {
chs := p.c.GetArray(fmt.Sprintf("gitea.%s.%s.channels", org, repo), []string{})
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg, bot.UnfurlLinks(false))
}
}

View File

@ -145,6 +145,6 @@ func (p *GitPlugin) githubEvent(w http.ResponseWriter, r *http.Request) {
chs := p.c.GetArray(fmt.Sprintf("github.%s.%s.channels", owner, repo), []string{})
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg, bot.UnfurlLinks(false))
}
}

View File

@ -53,6 +53,6 @@ func (p *GitPlugin) gitlabEvent(w http.ResponseWriter, r *http.Request) {
}
chs := p.c.GetArray(fmt.Sprintf("gitlab.%s.channels", owner), []string{})
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg, bot.UnfurlLinks(false))
}
}

View File

@ -42,6 +42,7 @@ type memeText struct {
XPerc float64 `json:"x"`
YPerc float64 `json:"y"`
Text string `json:"t"`
Caps bool `json:"c"`
}
var horizon = 24 * 7
@ -329,8 +330,8 @@ func (p *MemePlugin) findFontSize(config []memeText, w, h int, sizes []float64)
func defaultFormatConfig() []memeText {
return []memeText{
{XPerc: 0.5, YPerc: 0.05},
{XPerc: 0.5, YPerc: 0.95},
{XPerc: 0.5, YPerc: 0.05, Caps: true},
{XPerc: 0.5, YPerc: 0.95, Caps: true},
}
}
@ -395,6 +396,12 @@ func (p *MemePlugin) genMeme(meme string, bully image.Image, config []memeText)
fontLocation := p.c.Get("meme.font", "impact.ttf")
m.LoadFontFace(fontLocation, p.findFontSize(config, w, h, fontSizes))
for i, c := range config {
if c.Caps {
config[i].Text = strings.ToUpper(c.Text)
}
}
// Apply black stroke
m.SetHexColor("#000")
strokeSize := 6

View File

@ -25,7 +25,7 @@ var memeIndex = `
<b-navbar>
<b-navbar-brand>Memes</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Meme'">{{ "{{ item.Name }}" }}</b-nav-item>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Meme'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
@ -33,9 +33,53 @@ var memeIndex = `
variant="error"
v-if="err"
@dismissed="err = ''">
{{ "{{ err }}" }}
{{ err }}
</b-alert>
<b-form @submit="addMeme">
<b-form @submit="saveConfig" v-if="editConfig">
<b-container>
<b-row>
<b-col cols="1">
Name:
</b-col>
<b-col>
{{ editConfig.name }}
</b-col>
</b-row>
<b-row>
<b-col cols="1">
Image:
</b-col>
<b-col>
<img :src="editConfig.url" :alt="editConfig.url" rounded block fluid />
</b-col>
</b-row>
<b-row>
<b-col cols="1">
URL:
</b-col>
<b-col>
<b-input placeholder="URL..." v-model="editConfig.url"></b-input>
</b-col>
</b-row>
<b-row>
<b-col cols="1">
Config:
</b-col>
<b-col>
<b-form-textarea v-model="editConfig.config" rows="10">
</b-form-textarea>
</b-col>
</b-row>
<b-row>
<b-button type="submit" variant="primary">Save</b-button>
&nbsp;
<b-button @click="rm" variant="danger">Delete</b-button>
&nbsp;
<b-button type="cancel" @click="editConfig = null" variant="secondary">Cancel</b-button>
</b-row>
</b-container>
</b-form>
<b-form @submit="addMeme" v-if="!editConfig">
<b-container>
<b-row>
<b-col cols="3">
@ -58,7 +102,8 @@ var memeIndex = `
:items="results"
:fields="fields">
<template v-slot:cell(config)="data">
<pre>{{ "{{data.item.config}}" }}</pre>
<pre>{{data.item.config | pretty}}</pre>
<b-button @click="startEdit(data.item)">Edit</b-button>
</template>
<template v-slot:cell(image)="data">
<b-img :src="data.item.url" rounded block fluid />
@ -80,22 +125,33 @@ var memeIndex = `
router,
data: {
err: '',
nav: {{ .Nav }},
name: "",
url: "",
config: "",
nav: [],
name: '',
url: '',
config: '',
results: [],
editConfig: null,
fields: [
{ key: 'name', sortable: true },
{ key: 'url', sortable: true },
{ key: 'config' },
{ key: 'image' }
]
},
mounted() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
this.refresh();
},
computed: {
filters: {
pretty: function(value) {
if (!value) {
return ""
}
return JSON.stringify(JSON.parse(value), null, 2);
}
},
methods: {
refresh: function() {
@ -120,6 +176,36 @@ var memeIndex = `
this.refresh();
})
.catch(err => (this.err = err));
},
startEdit: function(item) {
this.editConfig = item;
},
saveConfig: function(evt) {
if (evt) {
evt.preventDefault();
evt.stopPropagation();
}
if (this.editConfig && this.editConfig.name && this.editConfig.url) {
axios.post('/meme/add', this.editConfig)
.then(resp => {
this.results = resp.data;
this.editConfig = null;
this.refresh();
})
.catch(err => this.err = err);
}
},
rm: function(evt) {
if (evt) {
evt.preventDefault();
evt.stopPropagation();
}
axios.delete('/meme/rm', { data: this.editConfig })
.then(resp => {
this.editConfig = null;
this.refresh();
})
.catch(err => this.err = err);
}
}
})

View File

@ -3,7 +3,6 @@ package meme
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"path"
@ -47,7 +46,7 @@ func (p *MemePlugin) all(w http.ResponseWriter, r *http.Request) {
for n, u := range memes {
config, ok := configs[n]
if !ok {
b, _ := json.Marshal(defaultFormatConfig)
b, _ := json.Marshal(defaultFormatConfig())
config = string(b)
}
realURL, err := url.Parse(u)
@ -130,8 +129,7 @@ func (p *MemePlugin) addMeme(w http.ResponseWriter, r *http.Request) {
}
func (p *MemePlugin) webRoot(w http.ResponseWriter, r *http.Request) {
var tpl = template.Must(template.New("factoidIndex").Parse(string(memeIndex)))
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
fmt.Fprint(w, memeIndex)
}
func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) {