Compare commits

..

7 Commits

Author SHA1 Message Date
Martyn 3950f8b672 Merge branch 'improvements' of martyn/karaokards into master
continuous-integration/drone/tag Build was killed Details
2020-02-21 19:56:31 +00:00
Martyn 6e7c52879c database and build date
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 20:55:19 +01:00
Martyn fad3079132 All the magic, database and admin stuff
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 20:54:27 +01:00
Martyn e989b8454b 404s, admin panel route and removal of old data structure
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 20:52:19 +01:00
Martyn 408516ecea External url
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 20:51:17 +01:00
Martyn 4d3b8a33e5 All the deployment updates
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 19:28:11 +01:00
Martyn ba1c9438f3 Makefile for build date
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-21 19:07:08 +01:00
8 changed files with 150 additions and 26 deletions

14
Makefile Executable file
View File

@ -0,0 +1,14 @@
BUILD=`date +%FT%T%z`
LDFLAGS=-ldflags "-X main.buildDate=${BUILD}"
.PHONY: build deps static
build:
go build ${LDFLAGS}
deps:
go get
static:
CGO_ENABLED=0 GOOS=linux go build ${LDFLAGS} -a -installsuffix cgo -o karaokards .

View File

@ -2,9 +2,8 @@ FROM golang@sha256:cee6f4b901543e8e3f20da3a4f7caac6ea643fd5a46201c3c2387183a332d
RUN apk update && apk add --no-cache git make ca-certificates && update-ca-certificates RUN apk update && apk add --no-cache git make ca-certificates && update-ca-certificates
COPY main.go /go/src/git.martyn.berlin/martyn/karaokards/ COPY main.go /go/src/git.martyn.berlin/martyn/karaokards/
COPY internal/ /go/src/git.martyn.berlin/martyn/karaokards/internal/ COPY internal/ /go/src/git.martyn.berlin/martyn/karaokards/internal/
RUN cd /go/src/git.martyn.berlin/martyn/karaokards/; go get; CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o karaokards . COPY Makefile /go/src/git.martyn.berlin/martyn/karaokards/
#RUN ls /go/src/github.com/karaokards/ -l RUN cd /go/src/git.martyn.berlin/martyn/karaokards/; make deps ; make static
FROM scratch FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

View File

@ -1,3 +1,4 @@
{ {
"channels": ["iMartynOnTwitch"] "channels": ["iMartynOnTwitch"],
"externalUrl": "karaokards.ing.martyn.berlin"
} }

View File

@ -1,12 +1,12 @@
apiVersion: v1 apiVersion: v1
data: data:
strings.json: "{\r\n \"strings\": [\r\n\t \"Let Pineboy choose!\",\r\n\t \"They're config.json: |
from the North\",\r\n \"Refers to food\"\r\n ]\r\n}" {
"channels": ["iMartynOnTwitch"],
"externalUrl": "karaokards.ing.martyn.berlin"
}
strings.json: "{\r\n \"strings\": [\r\n\t \"They're from the North\",\r\n
\ \"Refers to food\"\r\n ]\r\n}\n"
kind: ConfigMap kind: ConfigMap
metadata: metadata:
creationTimestamp: "2020-01-27T20:04:58Z" name: kardconfig
name: extracards
namespace: karaokards
resourceVersion: "48537355"
selfLink: /api/v1/namespaces/karaokards/configmaps/extracards
uid: 4b1d73b0-4140-11ea-94d8-9cb6540931b5

View File

@ -24,7 +24,7 @@ spec:
run: kardbot run: kardbot
spec: spec:
containers: containers:
- image: imartyn/karaokardbot:0.0.3-linux-amd64 - image: imartyn/karaokardbot:devel
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
name: kardbot name: kardbot
ports: ports:
@ -39,6 +39,9 @@ spec:
- mountPath: /app/strings.json - mountPath: /app/strings.json
name: extracards name: extracards
subPath: strings.json subPath: strings.json
- mountPath: /app/config.json
name: config
subPath: config.json
- mountPath: /data - mountPath: /data
name: data name: data
dnsPolicy: ClusterFirst dnsPolicy: ClusterFirst
@ -56,8 +59,15 @@ spec:
items: items:
- key: strings.json - key: strings.json
path: strings.json path: strings.json
name: extracards name: kardconfig
name: extracards name: extracards
- configMap:
defaultMode: 420
items:
- key: config.json
path: config.json
name: kardconfig
name: config
- name: data - name: data
persistentVolumeClaim: persistentVolumeClaim:
claimName: kkard-data claimName: kkard-data

View File

@ -3,6 +3,7 @@ package irc
import ( import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -15,6 +16,8 @@ import (
"time" "time"
rgb "github.com/foresthoffman/rgblog" rgb "github.com/foresthoffman/rgblog"
scribble "github.com/nanobox-io/golang-scribble"
uuid "github.com/google/uuid"
) )
const PSTFormat = "Jan 2 15:04:05 PST" const PSTFormat = "Jan 2 15:04:05 PST"
@ -38,9 +41,6 @@ type OAuthCred struct {
// The developer application client ID. Used for API calls to Twitch. // The developer application client ID. Used for API calls to Twitch.
ClientID string `json:"client_id,omitempty"` ClientID string `json:"client_id,omitempty"`
// List of Channels to join
Channels []string `json:"channels,omitempty"`
} }
type KardBot struct { type KardBot struct {
@ -54,6 +54,16 @@ type KardBot struct {
Server string Server string
startTime time.Time startTime time.Time
Prompts []string Prompts []string
Database scribble.Driver
channelData map[string]ChannelData
}
type ChannelData struct {
Name string `json:"name"`
AdminKey string `json:"value,omitempty"`
CustomCommand string `json:"customcommand,omitempty"`
ExtraStrings string `json:"extrastrings,omitempty"`
JoinTime time.Time `json:"jointime"`
} }
// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it // Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it
@ -134,7 +144,7 @@ func (bb *KardBot) HandleChat() error {
} }
// channel-owner specific commands // channel-owner specific commands
if userName == bb.Channel { if userName == channel {
switch cmd { switch cmd {
case "tbdown": case "tbdown":
rgb.CPrintf( rgb.CPrintf(
@ -144,6 +154,14 @@ func (bb *KardBot) HandleChat() error {
bb.Disconnect() bb.Disconnect()
return nil return nil
case "wat":
magicCode := bb.readOrCreateChannelKey(channel)
rgb.CPrintf(
"[%s] Magic code is %s - https://karaokards.ing.martyn.berlin/admin/%s/%s\n",
TimeStamp(),
magicCode, userName, magicCode,
)
bb.Say("Ack.")
default: default:
// do nothing // do nothing
} }
@ -232,15 +250,24 @@ func (bb *KardBot) Start() {
err := bb.ReadCredentials() err := bb.ReadCredentials()
if nil != err { if nil != err {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting...") fmt.Println("Aborting!")
return
}
err = bb.readChannelData()
if nil != err {
fmt.Println(err)
fmt.Println("Aborting!")
return return
} }
for { for {
bb.Connect() bb.Connect()
bb.Login() bb.Login()
if len(bb.Credentials.Channels) > 0 { if len(bb.channelData) > 0 {
bb.JoinChannel(bb.Credentials.Channels...) for channelName := range(bb.channelData) {
bb.JoinChannel(channelName)
}
} else { } else {
bb.JoinChannel() bb.JoinChannel()
} }
@ -257,10 +284,67 @@ func (bb *KardBot) Start() {
} }
} }
func (bb *KardBot) readChannelData() error {
records, err := bb.Database.ReadAll("channelData")
if err != nil {
// no db? initialise one?
record := ChannelData{Name: bb.Channel, JoinTime: time.Now()}
rgb.YPrintf("[%s] No channel data for #%s exists, creating...\n", TimeStamp(), bb.Channel)
if err := bb.Database.Write("channelData", bb.Channel, record); err != nil {
return err
}
bb.channelData = make(map[string]ChannelData)
bb.channelData[bb.Channel] = record;
} else {
bb.channelData = make(map[string]ChannelData)
}
for _, data := range records {
record := ChannelData{}
err := json.Unmarshal([]byte(data), &record);
if err != nil {
return err
}
bb.channelData[record.Name] = record
}
return nil
}
func (bb *KardBot) readOrCreateChannelKey(channel string) string {
magicCode := ""
var err error
var record ChannelData
if record, ok := bb.channelData[channel]; !ok {
rgb.YPrintf("[%s] No channel data for #%s exists, creating\n", TimeStamp(), channel)
err = bb.Database.Read("channelData", channel, &record);
if err == nil {
bb.channelData[channel] = record
}
}
record = bb.channelData[channel]
if err != nil || record.AdminKey == "" {
rgb.YPrintf("[%s] No channel key for #%s exists, creating one\n", TimeStamp(), channel)
newuu, _ := uuid.NewRandom()
magicCode = base64.StdEncoding.EncodeToString([]byte(newuu.String()))
record.AdminKey = magicCode
if record.Name == "" {
record.Name = channel
}
if err := bb.Database.Write("channelData", channel, record); err != nil {
rgb.RPrintf("[%s] Error writing channel data for #%s\n", TimeStamp(), channel)
}
bb.channelData[record.Name] = record
rgb.YPrintf("[%s] Cached channel key for #%s\n", TimeStamp(), record.Name)
} else {
magicCode = record.AdminKey
rgb.YPrintf("[%s] Loaded data for #%s\n", TimeStamp(), channel)
}
return magicCode
}
func TimeStamp() string { func TimeStamp() string {
return TimeStampFmt(PSTFormat) return TimeStampFmt(PSTFormat)
} }
func TimeStampFmt(format string) string { func TimeStampFmt(format string) string {
return time.Now().Format(format) return time.Now().Format(format)
} }

View File

@ -26,6 +26,7 @@ func HealthHandler(response http.ResponseWriter, request *http.Request) {
func NotFoundHandler(response http.ResponseWriter, request *http.Request) { func NotFoundHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("X-Template-File", "html"+request.URL.Path) response.Header().Add("X-Template-File", "html"+request.URL.Path)
response.WriteHeader(404)
tmpl := template.Must(template.ParseFiles("web/404.html")) tmpl := template.Must(template.ParseFiles("web/404.html"))
tmpl.Execute(response, nil) tmpl.Execute(response, nil)
} }
@ -72,7 +73,7 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
// NotFoundHandler(response, request) // NotFoundHandler(response, request)
// return // return
} }
var td = TemplateData{ircBot.Prompts[rand.Intn(len(ircBot.Prompts))], len(ircBot.Prompts), len(ircBot.Credentials.Channels), 0} var td = TemplateData{ircBot.Prompts[rand.Intn(len(ircBot.Prompts))], len(ircBot.Prompts), 0, 0}
err = tmpl.Execute(response, td) err = tmpl.Execute(response, td)
if err != nil { if err != nil {
http.Error(response, err.Error(), http.StatusInternalServerError) http.Error(response, err.Error(), http.StatusInternalServerError)
@ -80,6 +81,11 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
} }
} }
func AdminHandler(response http.ResponseWriter, request *http.Request) {
request.URL.Path = "/index.html"
TemplateHandler(response, request)
}
func HandleHTTP(passedIrcBot irc.KardBot) { func HandleHTTP(passedIrcBot irc.KardBot) {
ircBot = passedIrcBot ircBot = passedIrcBot
r := mux.NewRouter() r := mux.NewRouter()
@ -89,6 +95,7 @@ func HandleHTTP(passedIrcBot irc.KardBot) {
r.HandleFunc("/healthz", HealthHandler) r.HandleFunc("/healthz", HealthHandler)
r.HandleFunc("/example/{.*}", TemplateHandler) r.HandleFunc("/example/{.*}", TemplateHandler)
r.HandleFunc("/cover.css", CSSHandler) r.HandleFunc("/cover.css", CSSHandler)
r.HandleFunc("/admin/{channel}/{key}", AdminHandler)
http.Handle("/", r) http.Handle("/", r)
srv := &http.Server{ srv := &http.Server{
Handler: loggedRouter, Handler: loggedRouter,

15
main.go
View File

@ -70,7 +70,7 @@ func readConfig() {
rgb.RPrintf("[%s] Could not unmarshal `%s`. Unmarshal error: %s\n", irc.TimeStamp(), configFile, err) rgb.RPrintf("[%s] Could not unmarshal `%s`. Unmarshal error: %s\n", irc.TimeStamp(), configFile, err)
os.Exit(1) os.Exit(1)
} }
rgb.YPrintf("[%s] Read config file from `%s\n`", irc.TimeStamp(), configFile) rgb.YPrintf("[%s] Read config file from `%s`\n", irc.TimeStamp(), configFile)
return return
} }
@ -147,7 +147,10 @@ func readBonusStrings() []string {
return customStrings.Strings return customStrings.Strings
} }
var buildDate string
func main() { func main() {
rgb.YPrintf("[%s] starting karaokard bot build %s\n", irc.TimeStamp(), buildDate)
readConfig() readConfig()
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
for _, val := range builtins.Karaokards { for _, val := range builtins.Karaokards {
@ -156,6 +159,13 @@ func main() {
for _, val := range readBonusStrings() { for _, val := range readBonusStrings() {
selectablePrompts = append(selectablePrompts, val) selectablePrompts = append(selectablePrompts, val)
} }
persistentData := openDatabase()
var dbGlobalPrompts []string
if err := persistentData.Read("prompts", "global", &dbGlobalPrompts); err != nil {
persistentData.Write("prompts", "common", dbGlobalPrompts)
}
selectablePrompts := append(selectablePrompts, dbGlobalPrompts...)
rgb.YPrintf("[%s] %d prompts available.\n", irc.TimeStamp(), len(selectablePrompts)) rgb.YPrintf("[%s] %d prompts available.\n", irc.TimeStamp(), len(selectablePrompts))
oauthPath := "" oauthPath := ""
if config.OAuthPath == "" { if config.OAuthPath == "" {
@ -183,8 +193,6 @@ func main() {
} }
oauthPath = config.OAuthPath oauthPath = config.OAuthPath
} }
persistentData := openDatabase()
persistentData.Write("prompts", "common", selectablePrompts)
// Replace the channel name, bot name, and the path to the private directory with your respective // Replace the channel name, bot name, and the path to the private directory with your respective
// values. // values.
@ -196,6 +204,7 @@ func main() {
PrivatePath: oauthPath, PrivatePath: oauthPath,
Server: "irc.chat.twitch.tv", Server: "irc.chat.twitch.tv",
Prompts: selectablePrompts, Prompts: selectablePrompts,
Database: *persistentData,
} }
go func() { go func() {
rgb.YPrintf("[%s] Starting webserver on port %s\n", irc.TimeStamp(), "5353") rgb.YPrintf("[%s] Starting webserver on port %s\n", irc.TimeStamp(), "5353")