Multi-channel support and deployment for webserver.
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details

Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
This commit is contained in:
Martyn 2020-02-14 15:56:10 +01:00
parent deb12318b6
commit a9dc6db0a4
6 changed files with 62 additions and 29 deletions

View File

@ -10,4 +10,6 @@ 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/
COPY --from=builder /go/src/git.martyn.berlin/martyn/karaokards /app/ COPY --from=builder /go/src/git.martyn.berlin/martyn/karaokards /app/
COPY strings.json /app/strings.json COPY strings.json /app/strings.json
COPY web/ /app/web/
WORKDIR /app
CMD ["/app/karaokards"] CMD ["/app/karaokards"]

View File

@ -21,9 +21,9 @@ const PSTFormat = "Jan 2 15:04:05 PST"
// Regex for parsing PRIVMSG strings. // Regex for parsing PRIVMSG strings.
// //
// First matched group is the user's name and the second matched group is the content of the // First matched group is the user's name, second is the channel? and the third matched group is the content of the
// user's message. // user's message.
var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #\w+(?: :(.*))?$`) var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #(\w+)(?: :(.*))?$`)
// Regex for parsing user commands, from already parsed PRIVMSG strings. // Regex for parsing user commands, from already parsed PRIVMSG strings.
// //
@ -38,6 +38,9 @@ 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 {
@ -111,10 +114,11 @@ func (bb *KardBot) HandleChat() error {
if nil != matches { if nil != matches {
userName := matches[1] userName := matches[1]
msgType := matches[2] msgType := matches[2]
channel := matches[3]
switch msgType { switch msgType {
case "PRIVMSG": case "PRIVMSG":
msg := matches[3] msg := matches[4]
rgb.GPrintf("[%s] %s: %s\n", TimeStamp(), userName, msg) rgb.GPrintf("[%s] %s: %s\n", TimeStamp(), userName, msg)
// parse commands from user message // parse commands from user message
@ -124,9 +128,9 @@ func (bb *KardBot) HandleChat() error {
switch cmd { switch cmd {
case "card": case "card":
rgb.CPrintf("[%s] Card asked for!\n", TimeStamp()) rgb.CPrintf("[%s] Card asked for by %s on %s' channel!\n", TimeStamp(), userName, channel)
bb.Say("Your prompt is : " + bb.Prompts[rand.Intn(len(bb.Prompts))]) bb.Say("Your prompt is : "+bb.Prompts[rand.Intn(len(bb.Prompts))], channel)
} }
// channel-owner specific commands // channel-owner specific commands
@ -147,6 +151,7 @@ func (bb *KardBot) HandleChat() error {
} }
default: default:
// do nothing // do nothing
rgb.YPrintf("[%s] unknown IRC message : %s\n", TimeStamp(), line)
} }
} }
} }
@ -154,14 +159,24 @@ func (bb *KardBot) HandleChat() error {
} }
} }
// Makes the bot join its pre-specified channel. // Login to the IRC server
func (bb *KardBot) JoinChannel() { func (bb *KardBot) Login() {
rgb.YPrintf("[%s] Joining #%s...\n", TimeStamp(), bb.Channel) rgb.YPrintf("[%s] Logging into #%s...\n", TimeStamp(), bb.Channel)
bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n")) bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n"))
bb.conn.Write([]byte("NICK " + bb.Name + "\r\n")) bb.conn.Write([]byte("NICK " + bb.Name + "\r\n"))
bb.conn.Write([]byte("JOIN #" + bb.Channel + "\r\n")) }
rgb.YPrintf("[%s] Joined #%s as @%s!\n", TimeStamp(), bb.Channel, bb.Name) // Makes the bot join its pre-specified channel.
func (bb *KardBot) JoinChannel(channels ...string) {
if len(channels) == 0 {
channels = append(channels, bb.Channel)
}
for _, channel := range channels {
rgb.YPrintf("[%s] Joining #%s...\n", TimeStamp(), channel)
bb.conn.Write([]byte("JOIN #" + channel + "\r\n"))
rgb.YPrintf("[%s] Joined #%s as @%s!\n", TimeStamp(), channel, bb.Name)
}
} }
// Reads from the private credentials file and stores the data in the bot's Credentials field. // Reads from the private credentials file and stores the data in the bot's Credentials field.
@ -185,7 +200,7 @@ func (bb *KardBot) ReadCredentials() error {
} }
// Makes the bot send a message to the chat channel. // Makes the bot send a message to the chat channel.
func (bb *KardBot) Say(msg string) error { func (bb *KardBot) Say(msg string, channels ...string) error {
if "" == msg { if "" == msg {
return errors.New("BasicBot.Say: msg was empty.") return errors.New("BasicBot.Say: msg was empty.")
} }
@ -195,9 +210,17 @@ func (bb *KardBot) Say(msg string) error {
return errors.New("BasicBot.Say: msg exceeded 512 bytes") return errors.New("BasicBot.Say: msg exceeded 512 bytes")
} }
_, err := bb.conn.Write([]byte(fmt.Sprintf("PRIVMSG #%s :%s\r\n", bb.Channel, msg))) if len(channels) == 0 {
if nil != err { channels = append(channels, bb.Channel)
return err }
rgb.YPrintf("[%s] sending %s to channels %v as @%s!\n", TimeStamp(), msg, channels, bb.Name)
for _, channel := range channels {
_, err := bb.conn.Write([]byte(fmt.Sprintf("PRIVMSG #%s :%s\r\n", channel, msg)))
rgb.YPrintf("[%s] PRIVMSG #%s :%s\r\n", TimeStamp(), channel, msg)
if nil != err {
return err
}
} }
return nil return nil
} }
@ -215,7 +238,12 @@ func (bb *KardBot) Start() {
for { for {
bb.Connect() bb.Connect()
bb.JoinChannel() bb.Login()
if len(bb.Credentials.Channels) > 0 {
bb.JoinChannel(bb.Credentials.Channels...)
} else {
bb.JoinChannel()
}
err = bb.HandleChat() err = bb.HandleChat()
if nil != err { if nil != err {

View File

@ -1,9 +1,11 @@
package webserver package webserver
import ( import (
"math/rand"
irc "git.martyn.berlin/martyn/karaokards/internal/irc"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions"
"fmt" "fmt"
"html/template" "html/template"
@ -15,7 +17,7 @@ import (
//var store = sessions.NewCookieStore(os.Getenv("SESSION_KEY")) //var store = sessions.NewCookieStore(os.Getenv("SESSION_KEY"))
var store = sessions.NewCookieStore([]byte("GO_SESS")) var ircBot irc.KardBot
func HealthHandler(response http.ResponseWriter, request *http.Request) { func HealthHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("Content-type", "text/plain") response.Header().Add("Content-type", "text/plain")
@ -42,8 +44,10 @@ func RootHandler(response http.ResponseWriter, request *http.Request) {
func TemplateHandler(response http.ResponseWriter, request *http.Request) { func TemplateHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("X-Template-File", "web"+request.URL.Path) response.Header().Add("X-Template-File", "web"+request.URL.Path)
type TemplateData struct { type TemplateData struct {
Prompt string Prompt string
AvailCount int AvailCount int
ChannelCount int
MessageCount int
} }
// tmpl, err := template.New("html"+request.URL.Path).Funcs(template.FuncMap{ // tmpl, err := template.New("html"+request.URL.Path).Funcs(template.FuncMap{
// "ToUpper": strings.ToUpper, // "ToUpper": strings.ToUpper,
@ -68,7 +72,7 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
// NotFoundHandler(response, request) // NotFoundHandler(response, request)
// return // return
} }
var td = TemplateData{"Hello", 1} var td = TemplateData{ircBot.Prompts[rand.Intn(len(ircBot.Prompts))], len(ircBot.Prompts), len(ircBot.Credentials.Channels), 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)
@ -76,7 +80,8 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
} }
} }
func HandleHTTP() { func HandleHTTP(passedIrcBot irc.KardBot) {
ircBot = passedIrcBot
r := mux.NewRouter() r := mux.NewRouter()
loggedRouter := handlers.LoggingHandler(os.Stdout, r) loggedRouter := handlers.LoggingHandler(os.Stdout, r)
r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) r.NotFoundHandler = http.HandlerFunc(NotFoundHandler)

View File

@ -86,7 +86,7 @@ func main() {
} }
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")
webserver.HandleHTTP() webserver.HandleHTTP(myBot)
}() }()
myBot.Start() myBot.Start()
} }

View File

@ -4,9 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Templates files for the workshop"> <meta name="description" content="Karaokards">
<meta name="author" content="Martyn Ranyard"> <meta name="author" content="Martyn Ranyard">
<title>The k8s zoo</title> <title>The great unknown!</title>
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
@ -47,7 +47,6 @@
a{ a{
text-decoration-line: underline; text-decoration-line: underline;
} }
</style> </style>
</head> </head>
<body class="text-center"> <body class="text-center">
@ -64,8 +63,7 @@
<h1 class="cover-heading">Ooops!</h1> <h1 class="cover-heading">Ooops!</h1>
<p>It seems you've gone somewhere you shouldn't! 404 NOT FOUND!</p> <p>It seems you've gone somewhere you shouldn't! 404 NOT FOUND!</p>
<p/> <p/>
<p>This can happen if you enter the spotify id wrong and search, go back and check it!</p> <p>I'm not quite sure how you got here to be honest, if it was via a link on the site, let me know via twitch DM, if it was from someone else, let them know.</p>
<p class="lead">Spotify IDs look like this : 37i9dQZF1DX4UtSsGT1Sbe - 22 characters and can be got by clicking share and "Copy (Playlist|Album) Link".</p>
<p>Shameless self-promotion : Follow me on twitch - <a href="https://www.twitch.tv/iMartynOnTwitch">iMartynOnTwitch</a>, oddly enough, I do a lot of twitchsings!</p> <p>Shameless self-promotion : Follow me on twitch - <a href="https://www.twitch.tv/iMartynOnTwitch">iMartynOnTwitch</a>, oddly enough, I do a lot of twitchsings!</p>
</main> </main>
<footer class="mastfoot mt-auto"> <footer class="mastfoot mt-auto">

View File

@ -54,7 +54,7 @@
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column"> <div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<header class="masthead mb-auto"> <header class="masthead mb-auto">
<div class="inner"> <div class="inner">
<h3 class="masthead-brand">k8s-zoo</h3> <h3 class="masthead-brand">Karaokards</h3>
<nav class="nav nav-masthead justify-content-center"> <nav class="nav nav-masthead justify-content-center">
<a class="nav-link active" href="/">Home</a> <a class="nav-link active" href="/">Home</a>
</nav> </nav>
@ -63,7 +63,7 @@
<main role="main" class="inner cover"> <main role="main" class="inner cover">
<h1 class="cover-heading">Karaokards!!!</h1> <h1 class="cover-heading">Karaokards!!!</h1>
<p>Random prompt for you : {{.Prompt}}</p> <p>Random prompt for you : {{.Prompt}}</p>
<p>There are a total of {{.AvailCount}} prompts available.</p> <p>There are a total of {{.AvailCount}} prompts available. This bot is hanging out in {{.ChannelCount}} channels and has served {{.MessageCount}} prompts via twitch chat!</p>
</main> </main>
<footer class="mastfoot mt-auto"> <footer class="mastfoot mt-auto">
<div class="inner"> <div class="inner">