mirror of https://github.com/velour/catbase.git
Merge branch 'master' into stock
This commit is contained in:
commit
937d74afec
69
bot/bot.go
69
bot/bot.go
|
@ -3,10 +3,12 @@
|
||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -38,12 +40,19 @@ type bot struct {
|
||||||
version string
|
version string
|
||||||
|
|
||||||
// The entries to the bot's HTTP interface
|
// The entries to the bot's HTTP interface
|
||||||
httpEndPoints map[string]string
|
httpEndPoints []EndPoint
|
||||||
|
|
||||||
// filters registered by plugins
|
// filters registered by plugins
|
||||||
filters map[string]func(string) string
|
filters map[string]func(string) string
|
||||||
|
|
||||||
callbacks CallbackMap
|
callbacks CallbackMap
|
||||||
|
|
||||||
|
password string
|
||||||
|
passwordCreated time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndPoint struct {
|
||||||
|
Name, URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variable represents a $var replacement
|
// Variable represents a $var replacement
|
||||||
|
@ -73,7 +82,7 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
me: users[0],
|
me: users[0],
|
||||||
logIn: logIn,
|
logIn: logIn,
|
||||||
logOut: logOut,
|
logOut: logOut,
|
||||||
httpEndPoints: make(map[string]string),
|
httpEndPoints: make([]EndPoint, 0),
|
||||||
filters: make(map[string]func(string) string),
|
filters: make(map[string]func(string) string),
|
||||||
callbacks: make(CallbackMap),
|
callbacks: make(CallbackMap),
|
||||||
}
|
}
|
||||||
|
@ -133,46 +142,6 @@ func (b *bot) Who(channel string) []user.User {
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootIndex = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Factoids</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
{{if .EndPoints}}
|
|
||||||
<div style="padding-top: 1em;">
|
|
||||||
<table class="pure-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Plugin</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{range $key, $value := .EndPoints}}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{$value}}">{{$key}}</a></td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
|
|
||||||
context := make(map[string]interface{})
|
|
||||||
context["EndPoints"] = b.httpEndPoints
|
|
||||||
t, err := template.New("rootIndex").Parse(rootIndex)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err)
|
|
||||||
}
|
|
||||||
t.Execute(w, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCmd checks if message is a command and returns its curtailed version
|
// IsCmd checks if message is a command and returns its curtailed version
|
||||||
func IsCmd(c *config.Config, message string) (bool, string) {
|
func IsCmd(c *config.Config, message string) (bool, string) {
|
||||||
cmdcs := c.GetArray("CommandChar", []string{"!"})
|
cmdcs := c.GetArray("CommandChar", []string{"!"})
|
||||||
|
@ -266,5 +235,17 @@ func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) RegisterWeb(root, name string) {
|
func (b *bot) RegisterWeb(root, name string) {
|
||||||
b.httpEndPoints[name] = root
|
b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) GetPassword() string {
|
||||||
|
if b.passwordCreated.Before(time.Now().Add(-24 * time.Hour)) {
|
||||||
|
adjs := b.config.GetArray("bot.passwordAdjectives", []string{"very"})
|
||||||
|
nouns := b.config.GetArray("bot.passwordNouns", []string{"noun"})
|
||||||
|
verbs := b.config.GetArray("bot.passwordVerbs", []string{"do"})
|
||||||
|
a, n, v := adjs[rand.Intn(len(adjs))], nouns[rand.Intn(len(nouns))], verbs[rand.Intn(len(verbs))]
|
||||||
|
b.passwordCreated = time.Now()
|
||||||
|
b.password = fmt.Sprintf("%s-%s-%s", a, n, v)
|
||||||
|
}
|
||||||
|
return b.password
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ type Bot interface {
|
||||||
RegisterFilter(string, func(string) string)
|
RegisterFilter(string, func(string) string)
|
||||||
RegisterWeb(string, string)
|
RegisterWeb(string, string)
|
||||||
DefaultConnector() Connector
|
DefaultConnector() Connector
|
||||||
|
GetWebNavigation() []EndPoint
|
||||||
|
GetPassword() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connector represents a server connection to a chat service
|
// Connector represents a server connection to a chat service
|
||||||
|
|
|
@ -32,6 +32,7 @@ func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB }
|
||||||
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
||||||
func (mb *MockBot) WhoAmI() string { return "tester" }
|
func (mb *MockBot) WhoAmI() string { return "tester" }
|
||||||
func (mb *MockBot) DefaultConnector() Connector { return nil }
|
func (mb *MockBot) DefaultConnector() Connector { return nil }
|
||||||
|
func (mb *MockBot) GetPassword() string { return "12345" }
|
||||||
func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, error) {
|
func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, error) {
|
||||||
switch kind {
|
switch kind {
|
||||||
case Message:
|
case Message:
|
||||||
|
@ -52,6 +53,7 @@ func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, er
|
||||||
func (mb *MockBot) AddPlugin(f Plugin) {}
|
func (mb *MockBot) AddPlugin(f Plugin) {}
|
||||||
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
|
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
|
||||||
func (mb *MockBot) RegisterWeb(_, _ string) {}
|
func (mb *MockBot) RegisterWeb(_, _ string) {}
|
||||||
|
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
|
||||||
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
|
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebNavigation returns a list of bootstrap-vue <b-nav-item> links
|
||||||
|
// The parent <nav> is not included so each page may display it as
|
||||||
|
// best fits
|
||||||
|
func (b *bot) GetWebNavigation() []EndPoint {
|
||||||
|
endpoints := b.httpEndPoints
|
||||||
|
moreEndpoints := b.config.GetArray("bot.links", []string{})
|
||||||
|
for _, e := range moreEndpoints {
|
||||||
|
link := strings.SplitN(e, ":", 2)
|
||||||
|
if len(link) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, EndPoint{link[0], link[1]})
|
||||||
|
}
|
||||||
|
return endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootIndex = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/vue-router"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Factoids</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<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-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
|
@ -185,27 +185,29 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
isItMe := msg.BotID != "" && msg.BotID == s.myBotID
|
isItMe := msg.BotID != "" && msg.BotID == s.myBotID
|
||||||
|
m := s.buildMessage(msg)
|
||||||
|
if m.Time.Before(s.lastRecieved) {
|
||||||
|
log.Debug().
|
||||||
|
Time("ts", m.Time).
|
||||||
|
Interface("lastRecv", s.lastRecieved).
|
||||||
|
Msg("Ignoring message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := s.log(m); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error logging message")
|
||||||
|
}
|
||||||
if !isItMe && msg.ThreadTimeStamp == "" {
|
if !isItMe && msg.ThreadTimeStamp == "" {
|
||||||
m := s.buildMessage(msg)
|
s.lastRecieved = m.Time
|
||||||
if m.Time.Before(s.lastRecieved) {
|
s.event(s, bot.Message, m)
|
||||||
log.Debug().
|
|
||||||
Time("ts", m.Time).
|
|
||||||
Interface("lastRecv", s.lastRecieved).
|
|
||||||
Msg("Ignoring message")
|
|
||||||
} else {
|
|
||||||
if err := s.log(m); err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error logging message")
|
|
||||||
}
|
|
||||||
s.lastRecieved = m.Time
|
|
||||||
s.event(s, bot.Message, m)
|
|
||||||
}
|
|
||||||
} else if msg.ThreadTimeStamp != "" {
|
} else if msg.ThreadTimeStamp != "" {
|
||||||
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
||||||
s.event(s, bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp)
|
s.event(s, bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp)
|
||||||
|
} else if isItMe {
|
||||||
|
s.event(s, bot.SelfMessage, m)
|
||||||
} else {
|
} else {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("text", msg.Text).
|
Str("text", msg.Text).
|
||||||
Msg("THAT MESSAGE WAS HIDDEN")
|
Msg("Unknown message is hidden")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,12 +410,16 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
name := "UNKNOWN"
|
name := "UNKNOWN"
|
||||||
u, _ := s.getUser(m.User)
|
u, _ := s.getUser(m.User)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
name = u.Name
|
name = u.Profile.DisplayName
|
||||||
}
|
}
|
||||||
if m.Username != "" && u == nil {
|
if m.Username != "" && u == nil {
|
||||||
name = m.Username
|
name = m.Username
|
||||||
}
|
}
|
||||||
ch, _ := s.getChannel(m.Channel)
|
|
||||||
|
chName := m.Channel
|
||||||
|
if ch, _ := s.getChannel(m.Channel); ch != nil {
|
||||||
|
chName = ch.Name
|
||||||
|
}
|
||||||
|
|
||||||
tstamp := slackTStoTime(m.TimeStamp)
|
tstamp := slackTStoTime(m.TimeStamp)
|
||||||
|
|
||||||
|
@ -425,7 +431,7 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
Body: text,
|
Body: text,
|
||||||
Raw: m,
|
Raw: m,
|
||||||
Channel: m.Channel,
|
Channel: m.Channel,
|
||||||
ChannelName: ch.Name,
|
ChannelName: chName,
|
||||||
IsIM: m.ChannelType == "im",
|
IsIM: m.ChannelType == "im",
|
||||||
Command: isCmd,
|
Command: isCmd,
|
||||||
Action: isAction,
|
Action: isAction,
|
||||||
|
|
|
@ -5,6 +5,7 @@ package admin
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -80,6 +81,11 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(body) == "password" {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, p.bot.GetPassword())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
parts := strings.Split(body, " ")
|
parts := strings.Split(body, " ")
|
||||||
if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] {
|
if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] {
|
||||||
p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key")
|
p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key")
|
||||||
|
@ -160,8 +166,10 @@ func (p *AdminPlugin) registerWeb() {
|
||||||
p.bot.RegisterWeb("/vars", "Variables")
|
p.bot.RegisterWeb("/vars", "Variables")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(varIndex))
|
||||||
|
|
||||||
func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, varIndex)
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -21,13 +21,18 @@ var varIndex = `
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Vars</h1>
|
<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-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
<b-alert
|
<b-alert
|
||||||
dismissable
|
dismissable
|
||||||
variant="error"
|
variant="error"
|
||||||
v-if="err"
|
v-if="err"
|
||||||
@dismissed="err = ''">
|
@dismissed="err = ''">
|
||||||
{{ err }}
|
{{ "{{ err }}" }}
|
||||||
</b-alert>
|
</b-alert>
|
||||||
<b-container>
|
<b-container>
|
||||||
<b-table
|
<b-table
|
||||||
|
@ -42,8 +47,9 @@ var varIndex = `
|
||||||
var app = new Vue({
|
var app = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
vars: [],
|
|
||||||
err: '',
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
vars: [],
|
||||||
sortBy: 'key',
|
sortBy: 'key',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: { sortable: true } },
|
{ key: { sortable: true } },
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -41,8 +42,9 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := struct {
|
info := struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Payload string `json:"payload"`
|
Payload string `json:"payload"`
|
||||||
|
Password string `json:"password"`
|
||||||
}{}
|
}{}
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
err := decoder.Decode(&info)
|
err := decoder.Decode(&info)
|
||||||
|
@ -54,6 +56,12 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Interface("postbody", info).
|
Interface("postbody", info).
|
||||||
Msg("Got a POST")
|
Msg("Got a POST")
|
||||||
|
if info.Password != p.bot.GetPassword() {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
|
||||||
|
w.Write(j)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p.bot.Receive(p, bot.Message, msg.Message{
|
p.bot.Receive(p, bot.Message, msg.Message{
|
||||||
User: &user.User{
|
User: &user.User{
|
||||||
|
@ -81,8 +89,10 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(indexHTML))
|
||||||
|
|
||||||
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, indexHTML)
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completing the Connector interface, but will not actually be a connector
|
// Completing the Connector interface, but will not actually be a connector
|
||||||
|
|
|
@ -21,24 +21,23 @@ var indexHTML = `
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>CLI</h1>
|
<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-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
<b-alert
|
<b-alert
|
||||||
dismissable
|
dismissable
|
||||||
variant="error"
|
variant="error"
|
||||||
v-if="err"
|
:show="err">
|
||||||
@dismissed="err = ''">
|
{{ "{{ err }}" }}
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
</b-alert>
|
||||||
<b-container>
|
<b-container>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-form-group
|
<b-col cols="5">Password:</b-col>
|
||||||
:label="humanTest"
|
<b-col><b-input v-model="answer"></b-col>
|
||||||
label-for="input-1"
|
</b-row>
|
||||||
label-cols="8"
|
|
||||||
autofocus>
|
|
||||||
<b-input v-model="answer" id="input-1" autocomplete="off"></b-input>
|
|
||||||
</b-form-group>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-form-textarea
|
<b-form-textarea
|
||||||
v-sticky-scroll
|
v-sticky-scroll
|
||||||
|
@ -80,30 +79,20 @@ var indexHTML = `
|
||||||
var app = new Vue({
|
var app = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
answer: '',
|
answer: '',
|
||||||
correct: 0,
|
correct: 0,
|
||||||
textarea: [],
|
textarea: [],
|
||||||
user: '',
|
user: '',
|
||||||
input: '',
|
input: '',
|
||||||
err: '',
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
authenticated: function() {
|
authenticated: function() {
|
||||||
if (Number(this.answer) === this.correct && this.user !== '')
|
if (this.user !== '')
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
humanTest: function() {
|
|
||||||
const x = Math.floor(Math.random() * 100);
|
|
||||||
const y = Math.floor(Math.random() * 100);
|
|
||||||
const z = Math.floor(Math.random() * 100);
|
|
||||||
const ops = ['+', '-', '*'];
|
|
||||||
const op1 = ops[Math.floor(Math.random()*3)];
|
|
||||||
const op2 = ops[Math.floor(Math.random()*3)];
|
|
||||||
const eq = ""+x+op1+y+op2+z;
|
|
||||||
this.correct = eval(eq);
|
|
||||||
return "Human test: What is " + eq + "?";
|
|
||||||
},
|
|
||||||
text: function() {
|
text: function() {
|
||||||
return this.textarea.join('\n');
|
return this.textarea.join('\n');
|
||||||
}
|
}
|
||||||
|
@ -118,20 +107,17 @@ var indexHTML = `
|
||||||
send(evt) {
|
send(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
this.input = "";
|
|
||||||
if (!this.authenticated) {
|
if (!this.authenticated) {
|
||||||
console.log("User is a bot.");
|
|
||||||
this.err = "User appears to be a bot.";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = {user: this.user, payload: this.input};
|
const payload = {user: this.user, payload: this.input, password: this.answer};
|
||||||
console.log("Would have posted to /cli/api:" + JSON.stringify(payload));
|
|
||||||
this.addText(this.user, this.input);
|
this.addText(this.user, this.input);
|
||||||
|
this.input = "";
|
||||||
axios.post('/cli/api', payload)
|
axios.post('/cli/api', payload)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
console.log(JSON.stringify(resp.data));
|
|
||||||
const data = resp.data;
|
const data = resp.data;
|
||||||
this.addText(data.user, data.payload.trim());
|
this.addText(data.user, data.payload.trim());
|
||||||
|
this.err = '';
|
||||||
})
|
})
|
||||||
.catch(err => (this.err = err));
|
.catch(err => (this.err = err));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package counter
|
package counter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -558,16 +557,19 @@ func (p *CounterPlugin) registerWeb() {
|
||||||
p.Bot.RegisterWeb("/counter", "Counter")
|
p.Bot.RegisterWeb("/counter", "Counter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(html))
|
||||||
|
|
||||||
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
|
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, html)
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {
|
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
info := struct {
|
info := struct {
|
||||||
User string
|
User string
|
||||||
Thing string
|
Thing string
|
||||||
Action string
|
Action string
|
||||||
|
Password string
|
||||||
}{}
|
}{}
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
err := decoder.Decode(&info)
|
err := decoder.Decode(&info)
|
||||||
|
@ -579,6 +581,12 @@ func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request)
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Interface("postbody", info).
|
Interface("postbody", info).
|
||||||
Msg("Got a POST")
|
Msg("Got a POST")
|
||||||
|
if info.Password != p.Bot.GetPassword() {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
|
||||||
|
w.Write(j)
|
||||||
|
return
|
||||||
|
}
|
||||||
item, err := GetItem(p.DB, info.User, info.Thing)
|
item, err := GetItem(p.DB, info.User, info.Thing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
|
|
@ -19,32 +19,36 @@ var html = `
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Counters</h1>
|
<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-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
<b-alert
|
<b-alert
|
||||||
dismissable
|
dismissable
|
||||||
variant="error"
|
:show="err"
|
||||||
v-if="err"
|
variant="error">
|
||||||
@dismissed="err = ''">
|
{{ "{{ err }}" }}
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
</b-alert>
|
||||||
<b-container>
|
<b-container>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col cols="5">Human test: What is {{ equation }}?</b-col>
|
<b-col cols="5">Password:</b-col>
|
||||||
<b-col><b-input v-model="answer"></b-col>
|
<b-col><b-input v-model="answer"></b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row v-for="(counter, user) in counters">
|
<b-row v-for="(counter, user) in counters">
|
||||||
{{ user }}:
|
{{ "{{ user }}" }}:
|
||||||
<b-container>
|
<b-container>
|
||||||
<b-row v-for="(count, thing) in counter">
|
<b-row v-for="(count, thing) in counter">
|
||||||
<b-col offset="1">
|
<b-col offset="1">
|
||||||
{{ thing }}:
|
{{ "{{ thing }}" }}:
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col>
|
<b-col>
|
||||||
{{ count }}
|
{{ "{{ count }}" }}
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="2">
|
<b-col cols="2">
|
||||||
<button :disabled="!authenticated" @click="subtract(user,thing,count)">-</button>
|
<button @click="subtract(user,thing,count)">-</button>
|
||||||
<button :disabled="!authenticated" @click="add(user,thing,count)">+</button>
|
<button @click="add(user,thing,count)">+</button>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</b-container>
|
</b-container>
|
||||||
|
@ -67,59 +71,29 @@ var html = `
|
||||||
var app = new Vue({
|
var app = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
answer: '',
|
answer: '',
|
||||||
correct: 0,
|
correct: 0,
|
||||||
err: '',
|
counters: {}
|
||||||
counters: {
|
|
||||||
stk5: {
|
|
||||||
beer: 12,
|
|
||||||
tea: 84,
|
|
||||||
coffee: 127
|
|
||||||
},
|
|
||||||
flyngpngn: {
|
|
||||||
beer: 123,
|
|
||||||
mead: 1,
|
|
||||||
tea: 130
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
axios.get('/counter/api')
|
axios.get('/counter/api')
|
||||||
.then(resp => (this.counters = convertData(resp.data)))
|
.then(resp => (this.counters = convertData(resp.data)))
|
||||||
.catch(err => (this.err = err));
|
.catch(err => (this.err = err));
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
authenticated: function() {
|
|
||||||
if (Number(this.answer) === this.correct)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
equation: function() {
|
|
||||||
const x = Math.floor(Math.random() * 100);
|
|
||||||
const y = Math.floor(Math.random() * 100);
|
|
||||||
const z = Math.floor(Math.random() * 100);
|
|
||||||
const ops = ['+', '-', '*'];
|
|
||||||
const op1 = ops[Math.floor(Math.random()*3)];
|
|
||||||
const op2 = ops[Math.floor(Math.random()*3)];
|
|
||||||
const eq = ""+x+op1+y+op2+z;
|
|
||||||
this.correct = eval(eq);
|
|
||||||
return eq
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
add(user, thing, count) {
|
add(user, thing, count) {
|
||||||
this.counters[user][thing]++;
|
|
||||||
axios.post('/counter/api',
|
axios.post('/counter/api',
|
||||||
{user: user, thing: thing, action: '++'})
|
{user: user, thing: thing, action: '++', password: this.answer})
|
||||||
.then(resp => (this.counters = convertData(resp.data)))
|
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
|
||||||
.catch(err => (this.err = err));
|
.catch(err => this.err = err);
|
||||||
},
|
},
|
||||||
subtract(user, thing, count) {
|
subtract(user, thing, count) {
|
||||||
this.counters[user][thing]--;
|
|
||||||
axios.post('/counter/api',
|
axios.post('/counter/api',
|
||||||
{user: user, thing: thing, action: '--'})
|
{user: user, thing: thing, action: '--', password: this.answer})
|
||||||
.then(resp => (this.counters = convertData(resp.data)))
|
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
|
||||||
.catch(err => (this.err = err));
|
.catch(err => this.err = err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -807,6 +807,8 @@ func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(factoidIndex))
|
||||||
|
|
||||||
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
|
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, factoidIndex)
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,18 @@ var factoidIndex = `
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Factoids</h1>
|
<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-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
<b-alert
|
<b-alert
|
||||||
dismissable
|
dismissable
|
||||||
variant="error"
|
variant="error"
|
||||||
v-if="err"
|
v-if="err"
|
||||||
@dismissed="err = ''">
|
@dismissed="err = ''">
|
||||||
{{ err }}
|
{{ "{{ err }}" }}
|
||||||
</b-alert>
|
</b-alert>
|
||||||
<b-form @submit="runQuery">
|
<b-form @submit="runQuery">
|
||||||
<b-container>
|
<b-container>
|
||||||
|
@ -69,6 +74,7 @@ var factoidIndex = `
|
||||||
router,
|
router,
|
||||||
data: {
|
data: {
|
||||||
err: '',
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
query: '',
|
query: '',
|
||||||
results: [],
|
results: [],
|
||||||
fields: [
|
fields: [
|
||||||
|
|
Loading…
Reference in New Issue