diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..5763c53 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,20 @@ +kind: pipeline +name: default +workspace: + base: /go + path: src/discord-pocketbot-go +steps: +- name: test + image: golang + commands: + - go get + - go build +- name: docker + image: plugins/docker + settings: + repo: pocketjawa/discord-pocketbot + auto_tag: true + username: + from_secret: docker_username + password: + from_secret: docker_password \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3beee5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# ---> Go +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..debf7ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:latest AS build +WORKDIR /go/src/discord-pocketbot +COPY . . +RUN go get -d -v ./... +RUN CGO_ENABLED=0 GOOS=linux go install -v ./... + +FROM alpine:latest +run apk add --no-cache ca-certificates +WORKDIR /app +COPY --from=build /go/bin/discord-pocketbot discord-pocketbot +CMD ./discord-pocketbot \ No newline at end of file diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..bcf2917 --- /dev/null +++ b/commands.go @@ -0,0 +1,225 @@ +package main + +import ( + "context" + "encoding/json" + "github.com/bwmarrin/discordgo" + "github.com/bwmarrin/disgord/x/mux" + "github.com/nishanths/go-xkcd" + "io/ioutil" + "log" + "math/rand" + "net/http" + "os" + "regexp" + "strconv" + "strings" +) + +//Check member roles +func checkRole(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context, roleid string) bool { + if ctx.IsPrivate { + return false + } + channel, _ := s.Channel(m.ChannelID) + guild := channel.GuildID + member, _ := s.GuildMember(guild, m.Author.ID) + for _, role := range member.Roles { + if role == roleid { + return true + } + } + return false +} + +//Generate a heckin swear word +func getSwear(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + resp := "" + swear, err := http.Get("https://swear.jawa.moe/") + if err != nil { + resp = "Error fetching swear: " + err.Error() + } + defer swear.Body.Close() + + if swear.StatusCode == http.StatusOK { + body, err := ioutil.ReadAll(swear.Body) + if err != nil { + resp = "Error fetching swear: " + err.Error() + } + resp = string(body) + } + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Post the Maki Monday pic! +func makiMonday(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + resp := "https://maki.jawa.moe/monday.png" + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Post link to mixes B) +func postMixes(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + resp := "https://maki.jawa.moe/mixes/" + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Post a link to a random mix +func getRandomMix(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + + //Define mix details + type Mix struct { + Name string `json:"name"` + Type string `json:"type"` + Mtime string `json:"mtime"` + Size int `json:"size"` + } + + mixresp, err := http.Get("https://maki.jawa.moe/mixesjson/") + if err != nil { + resp := "Error fetching mixes: " + err.Error() + s.ChannelMessageSend(m.ChannelID, resp) + return + } + defer mixresp.Body.Close() + + if mixresp.StatusCode == http.StatusOK { + var mixes []*Mix + err = json.NewDecoder(mixresp.Body).Decode(&mixes) + rmix := mixes[rand.Intn(len(mixes))] + resp := "https://maki.jawa.moe/mixes/" + rmix.Name + s.ChannelMessageSend(m.ChannelID, resp) + } +} + +//Post XKCD comic! +func getXKCD(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + resp := "" + xkclient := xkcd.NewClient() + var err error + var comic xkcd.Comic + var arg string + if len(ctx.Fields) > 1 { + arg = ctx.Fields[1] + } else { + arg = "random" + } + matchedNum, _ := regexp.MatchString(`^[0-9]+$`, arg) + if arg == "latest" { + comic, err = xkclient.Latest(context.Background()) + } else if matchedNum { + comicNum, _ := strconv.Atoi(arg) + comic, err = xkclient.Get(context.Background(), comicNum) + } else { + latestcomic, _ := xkclient.Latest(context.Background()) + randcomic := rand.Intn(latestcomic.Number) + comic, err = xkclient.Get(context.Background(), randcomic) + } + if err != nil { + resp = err.Error() + } + resp = comic.ImageURL + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Restart the bot +func restartBot(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + if !checkRole(s, m, ctx, bot_admin_role) { + resp := "OwO you aren't my daddy..." + s.ChannelMessageSend(m.ChannelID, resp) + return + } + resp := "Goodnight ;-;" + s.ChannelMessageSend(m.ChannelID, resp) + Session.Close() + log.Println("brb dying at the request of ", m.Author.String()) + os.Exit(0) +} + +//Morgana yells at you to sleep... +func goToSleep(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + embed := &discordgo.MessageEmbed{ + Description: "Aren't you tired?\nLet's call it a day\nand get some sleep.", + Thumbnail: &discordgo.MessageEmbedThumbnail{ + URL: "https://cdn.discordapp.com/emojis/396429379686629378.png", + }, + Title: "Morgana", + } + s.ChannelMessageSendEmbed(m.ChannelID, embed) +} + +func setNowPlaying(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + if !checkRole(s, m, ctx, bot_admin_role) { + resp := "OwO you aren't my daddy..." + s.ChannelMessageSend(m.ChannelID, resp) + return + } + var resp string + if len(ctx.Fields) > 1 { + arg := strings.Join(ctx.Fields[1:], " ") + s.UpdateStatus(0, arg) + resp = "Now playing: " + arg + } else { + resp = "Please tell me what to play!" + } + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Roll any number of dice of the same kind +func diceRoller(dienum, diesides int) (total int, diceroles []string) { + for 0 < dienum { + dienum-- + rollresult := rand.Intn(diesides) + 1 + total += rollresult + diceroles = append(diceroles, "D"+strconv.Itoa(diesides)+": **"+strconv.Itoa(rollresult)+"**") + } + return +} + +//Dice rolling command +func rollDice(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + var resp string + var diceroles []string + var dicetotal int + var matchDicePattern = regexp.MustCompile(`^[0-9]*[Dd][1-9][0-9]*$`) + var args = ctx.Fields[1:] + for i := range args { + if matchDicePattern.MatchString(args[i]) { + dLocation := strings.Index(strings.ToLower(args[i]), "d") + dienum, _ := strconv.Atoi(args[i][:dLocation]) + diesides, _ := strconv.Atoi(args[i][dLocation+1:]) + if dienum == 0 { + dienum = 1 + } + resultsTotal, resultsRolls := diceRoller(dienum, diesides) + if dienum > 25 { + diceroles = append(diceroles, strconv.Itoa(dienum)+"*D"+strconv.Itoa(diesides)+": **"+strconv.Itoa(resultsTotal)+"**") + } else { + diceroles = append(diceroles, resultsRolls...) + } + dicetotal += resultsTotal + } + } + results := strings.Join(diceroles, ", ") + resp = m.Author.Username + " rolled " + results + ", for a total of **" + strconv.Itoa(dicetotal) + "**!" + if dicetotal == 0 { + resp = "Oops, looks like no dice were rolled! Try something like `!roll d20`, `!roll 2d6`, or `!roll 3d6 d20` instead." + } + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Ask the Magic Conch shell to predict the future! +func askConch(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + var resp string + if len(ctx.Fields) > 1 { + conchResponses := []string{"It is certain", "It is decidedly so", "Without a doubt", "Yes definitely", "You may rely on it", "As I see it, yes", "Most likely", "Outlook good", "Yes", "Signs point to yes", "Reply hazy try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again", "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful"} + resp = conchResponses[rand.Intn(len(conchResponses))] + } else { + resp = "The Magic Conchâ„¢ may be able to see into the future, but it can't read your mind! Please include a question." + } + s.ChannelMessageSend(m.ChannelID, resp) +} + +//Test command that says UwU +func sayUwU(s *discordgo.Session, m *discordgo.Message, ctx *mux.Context) { + s.ChannelMessageSend(m.ChannelID, "UwU *nuzzles*") +} diff --git a/pocketbot.go b/pocketbot.go new file mode 100644 index 0000000..3c6e632 --- /dev/null +++ b/pocketbot.go @@ -0,0 +1,96 @@ +package main + +import ( + "flag" + "fmt" + "github.com/bwmarrin/discordgo" + "github.com/bwmarrin/disgord/x/mux" + "log" + "math/rand" + "os" + "os/signal" + "syscall" + "time" +) + +var Session, _ = discordgo.New() +var bot_token string +var bot_channel string +var bot_admin_role string +var Router = mux.New() + +func init() { + rand.Seed(time.Now().UnixNano()) + flag.StringVar(&bot_token, "t", "", "Discord Authentication Token") + flag.StringVar(&bot_channel, "bc", "", "Bot status channel") + flag.Parse() + if os.Getenv("DISCORD_TOKEN") != "" { + Session.Token = "Bot " + os.Getenv("DISCORD_TOKEN") + } else { + Session.Token = "Bot " + bot_token + } + if os.Getenv("DISCORD_BOT_CHANNEL") != "" { + bot_channel = os.Getenv("DISCORD_BOT_CHANNEL") + } + if os.Getenv("POCKETBOT_ADMIN_ROLE") != "" { + bot_admin_role = os.Getenv("POCKETBOT_ADMIN_ROLE") + } +} + +func main() { + var err error + fmt.Println("pocketbot") + //flag.Parse() + if Session.Token == "" { + log.Println("You must provide a Discord auth token!") + return + } + + //Handlers + Session.AddHandler(ready) + Session.AddHandler(Router.OnMessageCreate) + + //Set the command prefix + if os.Getenv("POCKETBOT_PREFIX") != "" { + Router.Prefix = os.Getenv("POCKETBOT_PREFIX") + } else { + Router.Prefix = "!" + } + + // Register the build-in help command. + Router.Route("help", "Display this message.", Router.Help) + Router.Route("swear", "Make me swear!", getSwear) + Router.Route("maki", "It's Maki Monday my dudes!", makiMonday) + Router.Route("monday", "It's Maki Monday my dudes!", makiMonday) + Router.Route("mixes", "Post the link to my fire mixtapes!", postMixes) + Router.Route("rmix", "Post a random mix!", getRandomMix) + Router.Route("xkcd", "Post a specific, random, or the latest XKCD comic", getXKCD) + Router.Route("restart", "Restart the bot.", restartBot) + Router.Route("kill", "Restart the bot.", restartBot) + Router.Route("sleep", "Something about sleeping...", goToSleep) + Router.Route("setplaying", "Set the nowplaying message for the bot.", setNowPlaying) + Router.Route("roll", "Roll some dice!", rollDice) + Router.Route("conch", "Ask the Magic Conchâ„¢ shell to predict the future!", askConch) + Router.Route("uwu", "Say UwU", sayUwU) + + //Open a connection to Discord + err = Session.Open() + if err != nil { + log.Printf("Error opening connection to Discord, %s\n", err) + os.Exit(1) + } + + //Wait for a CTRL-C + log.Printf("Now running. Press CTRL-C to exit.") + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + <-sc + Session.Close() +} + +func ready(s *discordgo.Session, event *discordgo.Ready) { + s.UpdateStatus(0, "with droids!") + if os.Getenv("POCKETBOT_STARTUP_MESSAGE") == "TRUE" { + s.ChannelMessageSend(bot_channel, "This isn't Tatooine...") + } +} diff --git a/userdata/.gitignore b/userdata/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/userdata/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file