You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
10 KiB
354 lines
10 KiB
package main |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"mime/multipart" |
|
"net/http" |
|
"net/textproto" |
|
"os" |
|
"strings" |
|
"time" |
|
|
|
"github.com/nlopes/slack" |
|
"github.com/unixvoid/glogger" |
|
"gopkg.in/gcfg.v1" |
|
"gopkg.in/redis.v5" |
|
) |
|
|
|
type Config struct { |
|
Lorebot struct { |
|
Loglevel string |
|
BootstrapDelay time.Duration |
|
AuthFile string |
|
} |
|
Redis struct { |
|
Host string |
|
Password string |
|
} |
|
} |
|
|
|
var ( |
|
config = Config{} |
|
) |
|
|
|
func main() { |
|
// read in config file |
|
readConf() |
|
|
|
// initialize the logger with the configured loglevel |
|
initLogger(config.Lorebot.Loglevel) |
|
|
|
// initialize redis connection |
|
redisClient, err := initRedisConnection() |
|
if err != nil { |
|
glogger.Debug.Printf("redis conneciton cannot be made, trying again in %d seconds", config.Lorebot.BootstrapDelay) |
|
time.Sleep(config.Lorebot.BootstrapDelay * time.Second) |
|
redisClient, err = initRedisConnection() |
|
if err != nil { |
|
glogger.Error.Println("redis connection cannot be made.") |
|
os.Exit(1) |
|
} |
|
} |
|
glogger.Debug.Println("connection to redis succeeded.") |
|
glogger.Info.Println("link to redis on", config.Redis.Host) |
|
|
|
// read in creds |
|
credentials, _ := ioutil.ReadFile(config.Lorebot.AuthFile) |
|
lines := strings.Split(string(credentials), "\n") |
|
auth := lines[0] |
|
|
|
api := slack.New(auth) |
|
//api.SetDebug(true) |
|
|
|
rtm := api.NewRTM() |
|
go rtm.ManageConnection() |
|
|
|
Loop: |
|
for { |
|
select { |
|
case msg := <-rtm.IncomingEvents: |
|
switch ev := msg.Data.(type) { |
|
case *slack.MessageEvent: |
|
if ev.Type == "message" { |
|
glogger.Debug.Printf("processing: %s", ev.Text) |
|
// see if the message begins with '.', it could be a command |
|
if len(ev.Text) == 0 { |
|
// nil message, dont print |
|
//glogger.Debug.Println("breaking on blank message") |
|
break |
|
} else { |
|
comm := fmt.Sprintf("%c", ev.Text[0]) |
|
if comm == "." { |
|
if strings.Count(ev.Text, " ") == 0 { |
|
// no spaces, its a static command |
|
staticCommandHandler(rtm, ev, redisClient) |
|
} else { |
|
// spaces, dynamic handler |
|
dynamicCommandHandler(rtm, ev, redisClient) |
|
} |
|
} |
|
} |
|
} |
|
case *slack.RTMError: |
|
fmt.Printf("Error: %s\n", ev.Error()) |
|
case *slack.InvalidAuthEvent: |
|
fmt.Printf("Invalid credentials") |
|
break Loop |
|
default: |
|
// ignore all other events |
|
//fmt.Printf("Unknown error") |
|
//fmt.Printf("%v\n", msg.Data) |
|
} |
|
} |
|
} |
|
} |
|
|
|
func readConf() { |
|
// init config file |
|
err := gcfg.ReadFileInto(&config, "config.gcfg") |
|
if err != nil { |
|
panic(fmt.Sprintf("Could not load config.gcfg, error: %s\n", err)) |
|
} |
|
} |
|
|
|
func initLogger(logLevel string) { |
|
// init logger |
|
if logLevel == "debug" { |
|
glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr) |
|
} else if logLevel == "cluster" { |
|
glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr) |
|
} else if logLevel == "info" { |
|
glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr) |
|
} else { |
|
glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr) |
|
} |
|
} |
|
|
|
func initRedisConnection() (*redis.Client, error) { |
|
// init redis connection |
|
redisClient := redis.NewClient(&redis.Options{ |
|
Addr: config.Redis.Host, |
|
Password: config.Redis.Password, |
|
DB: 0, |
|
}) |
|
|
|
_, redisErr := redisClient.Ping().Result() |
|
return redisClient, redisErr |
|
} |
|
|
|
func dynamicCommandHandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { |
|
message := ev.Text |
|
if strings.Count(message, " ") < 1 { |
|
glogger.Debug.Println("not proper syntax") |
|
} |
|
|
|
// verify syntax |
|
s := strings.SplitN(message, " ", 2) |
|
command, content := s[0], s[1] |
|
trimmedPrefix := strings.TrimPrefix(command, ".") |
|
|
|
switch trimmedPrefix { |
|
case "lore": |
|
s := strings.SplitN(string(ev.Text), " ", 2) |
|
contenthandler(rtm, ev, s[1], "lore", redisClient) |
|
case "newlore": |
|
if strings.Contains(content, " ") { |
|
} else { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("not proper syntax", ev.Channel)) |
|
return |
|
} |
|
newhandler(rtm, ev, content, "lore", redisClient) |
|
case "rmlore": |
|
rmhandler(rtm, ev, content, "lore", redisClient) |
|
case "rek": |
|
s := strings.SplitN(string(ev.Text), " ", 2) |
|
rekhandler(rtm, ev, s[1], redisClient) |
|
case "gnu": |
|
s := strings.SplitN(string(ev.Text), " ", 2) |
|
gnuhandler(rtm, ev, s[1]) |
|
case "created": |
|
attrhandler(rtm, ev, "lore", content, "cdate", redisClient) |
|
case "owner": |
|
attrhandler(rtm, ev, "lore", content, "owner", redisClient) |
|
case "removedby": |
|
attrhandler(rtm, ev, "lore", content, "rmby", redisClient) |
|
case "removed": |
|
attrhandler(rtm, ev, "lore", content, "rmdate", redisClient) |
|
} |
|
} |
|
|
|
func staticCommandHandler(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { |
|
s := strings.SplitN(ev.Text, " ", 2) |
|
command := s[0] |
|
trimmedPrefix := strings.TrimPrefix(command, ".") |
|
|
|
switch trimmedPrefix { |
|
case "help": |
|
helpmsg(rtm, ev) |
|
case "help2": |
|
advancedhelpmsg(rtm, ev) |
|
case "rng": |
|
rtm.SendMessage(rtm.NewOutgoingMessage("command not yet supported", ev.Channel)) |
|
case "version": |
|
rtm.SendMessage(rtm.NewOutgoingMessage("command not yet supported", ev.Channel)) |
|
case "lorelist": |
|
listhandler(redisClient, rtm, ev, "lore", "added") |
|
case "removedlore": |
|
listhandler(redisClient, rtm, ev, "lore", "removed") |
|
case "commandlist": |
|
listhandler(redisClient, rtm, ev, "command", "added") |
|
case "removedcommands": |
|
listhandler(redisClient, rtm, ev, "command", "removed") |
|
case "lorestatus": |
|
lorestatus(rtm, ev, redisClient) |
|
} |
|
} |
|
|
|
func helpmsg(rtm *slack.RTM, ev *slack.MessageEvent) { |
|
help1 := "I am the lore archival bot, I support the following commands:\n" + |
|
"```" + |
|
".lore: view lore\n" + |
|
".newlore: add new lore\n" + |
|
".rmlore: remove lore\n" + |
|
".lorelist: view lore database\n" + |
|
"use '.help2' to see advanced lookup commands." + |
|
"```" |
|
|
|
rtm.SendMessage(rtm.NewOutgoingMessage(help1, ev.Channel)) |
|
} |
|
|
|
func advancedhelpmsg(rtm *slack.RTM, ev *slack.MessageEvent) { |
|
help2 := "```" + |
|
".created view creation timestamp\n" + |
|
".owner: who created the lore\n" + |
|
".removed: view removal timestamp\n" + |
|
".removedby: who removed the lore" + |
|
"```" |
|
|
|
rtm.SendMessage(rtm.NewOutgoingMessage(help2, ev.Channel)) |
|
} |
|
|
|
func contenthandler(rtm *slack.RTM, ev *slack.MessageEvent, message, queryType string, redisClient *redis.Client) { |
|
loreTerm := strings.Replace(message, ".lore", "", -1) |
|
println("searching for:", loreTerm) |
|
//rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("searching for '%s'\n", loreTerm), ev.Channel)) |
|
|
|
content, err := loreCheck(loreTerm, queryType, redisClient) |
|
if err == nil { |
|
// lore found, print it |
|
rtm.SendMessage(rtm.NewOutgoingMessage(content, ev.Channel)) |
|
} else { |
|
// lore not found, print error |
|
rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("lore '%s' not found\n", loreTerm), ev.Channel)) |
|
} |
|
} |
|
|
|
func attrhandler(rtm *slack.RTM, ev *slack.MessageEvent, queryType, queryAttr, message string, redisClient *redis.Client) { |
|
query, err := loreAttrQuery(message, queryType, queryAttr, redisClient) |
|
if err == nil { |
|
rtm.SendMessage(rtm.NewOutgoingMessage(query, ev.Channel)) |
|
} else { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("attribute does not exist", ev.Channel)) |
|
} |
|
} |
|
|
|
func rekhandler(rtm *slack.RTM, ev *slack.MessageEvent, target string, redisClient *redis.Client) { |
|
rektBit, _ := redisClient.SRandMember("index:rekt").Result() |
|
rekFmt := fmt.Sprintf("%s %s", target, rektBit) |
|
rtm.SendMessage(rtm.NewOutgoingMessage(rekFmt, ev.Channel)) |
|
} |
|
|
|
func gnuhandler(rtm *slack.RTM, ev *slack.MessageEvent, target string) { |
|
gnuFmt := fmt.Sprintf("slaps the nonfree software out of %s's hands", target) |
|
rtm.SendMessage(rtm.NewOutgoingMessage(gnuFmt, ev.Channel)) |
|
} |
|
|
|
func listhandler(redisClient *redis.Client, rtm *slack.RTM, ev *slack.MessageEvent, listType, listAttr string) { |
|
// aka index:command:added |
|
newType := fmt.Sprintf("%s:%s:%s", "index", listType, listAttr) |
|
//command, err := botfunc.Query(newType, redisClient) |
|
command, err := loreQuery(newType, redisClient) |
|
if err != nil { |
|
glogger.Error.Println(err) |
|
println(err) |
|
} |
|
fCommand := strings.Replace(command, " ", "\n", -1) |
|
fCommand = strings.Replace(fCommand, "\n", "", 1) |
|
|
|
// initialize buffer |
|
bodyBuf := &bytes.Buffer{} |
|
bodyWriter := multipart.NewWriter(bodyBuf) |
|
// create header and body. filename must be set |
|
mh := make(textproto.MIMEHeader) |
|
mh.Set("Content-Type", "application/octet-stream") |
|
mh.Set("Content-Disposition", "form-data; name=\"file\"; filename=\" \"") |
|
// write body with mime header |
|
partWriter, _ := bodyWriter.CreatePart(mh) |
|
io.Copy(partWriter, bytes.NewBufferString(fCommand)) |
|
contentType := bodyWriter.FormDataContentType() |
|
bodyWriter.Close() |
|
// set content type to multipart/form-data, required for bitnuke.io |
|
h := make(textproto.MIMEHeader) |
|
h.Set("Content-Type", "multipart/form-data") |
|
bodyWriter.CreatePart(h) |
|
bodyWriter.Close() |
|
|
|
// POST to bitnuke.io |
|
resp, err := http.Post("https://bitnuke.io/upload", contentType, bodyBuf) |
|
if err != nil { |
|
println(err) |
|
} |
|
defer resp.Body.Close() |
|
resp_body, err := ioutil.ReadAll(resp.Body) |
|
if err != nil { |
|
println(err) |
|
} |
|
// format response to be link |
|
fResponse := fmt.Sprintf("https://bitnuke.io/%s", resp_body[:]) |
|
// print to channel |
|
rtm.SendMessage(rtm.NewOutgoingMessage(fResponse, ev.Channel)) |
|
} |
|
|
|
func lorestatus(rtm *slack.RTM, ev *slack.MessageEvent, redisClient *redis.Client) { |
|
command, _ := loreQuery("index:lore:added", redisClient) |
|
loreammount := strings.Count(command, " ") |
|
status := fmt.Sprintf("the lore db has %d entries", loreammount-1) |
|
rtm.SendMessage(rtm.NewOutgoingMessage(status, ev.Channel)) |
|
} |
|
|
|
func newhandler(rtm *slack.RTM, ev *slack.MessageEvent, message, newType string, redisClient *redis.Client) { |
|
// get nick from slack api |
|
user, err := rtm.GetUserInfo(ev.User) |
|
if err != nil { |
|
fmt.Printf("%s\n", err) |
|
return |
|
} |
|
nick := fmt.Sprintf("%s", user.Name) |
|
|
|
err = loreNewString(nick, message, newType, redisClient) |
|
if err == nil { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("entry created", ev.Channel)) |
|
} else { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("entry could not be created", ev.Channel)) |
|
} |
|
} |
|
|
|
func rmhandler(rtm *slack.RTM, ev *slack.MessageEvent, message, rmType string, redisClient *redis.Client) { |
|
// get nick from slack api |
|
user, err := rtm.GetUserInfo(ev.User) |
|
if err != nil { |
|
fmt.Printf("%s\n", err) |
|
return |
|
} |
|
nick := fmt.Sprintf("%s", user.Name) |
|
|
|
err = loreRmString(nick, message, rmType, redisClient) |
|
if err == nil { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("entry removed", ev.Channel)) |
|
} else { |
|
rtm.SendMessage(rtm.NewOutgoingMessage("entry could not be removed", ev.Channel)) |
|
} |
|
}
|
|
|