A => LICENSE +28 -0
@@ 1,28 @@
+Copyright (c) 2015, Bruce Marriner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of discordgo nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
A => README.md +47 -0
@@ 1,47 @@
+# Disgord
+[](http://goreportcard.com/report/bwmarrin/disgord) [](https://travis-ci.org/bwmarrin/disgord) [](https://discord.gg/0f1SbxBZjYq9jLBk)
+<img align="right" src="https://raw.githubusercontent.com/wiki/bwmarrin/disgord/images/gourd.jpg">
+
+Dis**go**rd is an example of or starting point for creating an easy to use and
+extensible Discord bot using the [DiscordGo](https://github.com/bwmarrin/discordgo)
+library.
+
+If you would like to help the Disgord or DiscordGo package please use
+[this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
+to add the official DiscordGo test bot **dgo** to your server. This provides
+indispensable help to this project.
+
+**For help with this program or general Go discussion, please join the [Discord
+Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.**
+
+## Getting Started
+
+The below assumes you already have a working Go environment, if not please see
+[this page](https://golang.org/doc/install) first.
+
+### Installing
+
+Use go get to download the repository into your Go work environment.
+
+```sh
+go get github.com/bwmarrin/discordgo
+```
+
+### Usage
+```sh
+cd $GOPATH/src/github.com/bwmarrin/disgord
+go build
+./disgord -t BOT_AUTH_TOKEN
+```
+
+
+## Documentation
+
+Glenda Bot invite: https://discordapp.com/oauth2/authorize?client_id=406483821144571915&scope=bot
+
+## Contributing
+
+This repository is more of a placeholder than anything else right now. It's probably
+not in a good position for taking any contributions. If you have questions or
+want to be involved please find me on the Discord Gophers server.
+
A => disgord +0 -0
A => main.go +82 -0
@@ 1,82 @@
+// Declare this file to be part of the main package so it can be compiled into
+// an executable.
+package main
+
+// Import all Go packages required for this file.
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/bwmarrin/discordgo"
+)
+
+// Version is a constant that stores the Disgord version information.
+const Version = "v0.0.0-alpha"
+
+// Session is declared in the global space so it can be easily used
+// throughout this program.
+// In this use case, there is no error that would be returned.
+var Session, _ = discordgo.New()
+
+// Read in all configuration options from both environment variables and
+// command line arguments.
+func init() {
+
+ // Discord Authentication Token
+ Session.Token = os.Getenv("DG_TOKEN")
+ if Session.Token == "" {
+ flag.StringVar(&Session.Token, "t", "", "Discord Authentication Token")
+ }
+}
+
+func main() {
+
+ // Declare any variables needed later.
+ var err error
+
+ // Print out a fancy logo!
+ fmt.Printf(`
+ ________ .__ .___
+ \______ \ |__| ______ ____ ___________ __| _/
+ || | \| |/ ___// ___\ / _ \_ __ \/ __ |
+ || ' \ |\___ \/ /_/ > <_> ) | \/ /_/ |
+ ||______ /__/____ >___ / \____/|__| \____ |
+ \_______\/ \/_____/ %-16s\/`+"\n\n", Version)
+
+ // Parse command line arguments
+ flag.Parse()
+
+ // Verify a Token was provided
+ if Session.Token == "" {
+ log.Println("You must provide a Discord authentication token.")
+ return
+ }
+
+ // Verify the Token is valid and grab user information
+ Session.State.User, err = Session.User("@me")
+ if err != nil {
+ log.Printf("error fetching user information, %s\n", err)
+ }
+
+ // Open a websocket 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
+
+ // Clean up
+ Session.Close()
+
+ // Exit Normally.
+}
A => mux.go +20 -0
@@ 1,20 @@
+package main
+
+// This file adds the Disgord message route multiplexer, aka "command router".
+// to the Disgord bot. This is an optional addition however it is included
+// by default to demonstrate how to extend the Disgord bot.
+
+import "github.com/bwmarrin/disgord/x/mux"
+
+// Router is registered as a global variable to allow easy access to the
+// multiplexer throughout the bot.
+var Router = mux.New()
+
+func init() {
+ // Register the mux OnMessageCreate handler that listens for and processes
+ // all messages received.
+ Session.AddHandler(Router.OnMessageCreate)
+
+ // Register the build-in help command.
+ Router.Route("help", "Display this message.", Router.Help)
+}
A => x/README.md +19 -0
@@ 1,19 @@
+# Disgord Experimental
+[](https://discord.gg/0f1SbxBZjYq9jLBk)
+<img align="right" src="https://raw.githubusercontent.com/wiki/bwmarrin/disgord/images/gourd.jpg">
+
+Dis**go**rd is an example of or starting point for creating an easy to use and
+extensible Discord bot using the [DiscordGo](https://github.com/bwmarrin/discordgo)
+library.
+
+**This folder contains various experimental packages that can be used with Disgord
+or DiscordGo. The packages here may change wildly - so be sure you look for
+changes before pulling in updates.**
+
+If you would like to help the Disgord or DiscordGo package please use
+[this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
+to add the official DiscordGo test bot **dgo** to your server. This provides
+indispensable help to this project.
+
+**For help with this program or general Go discussion, please join the [Discord
+Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.**
A => x/mux/README.md +29 -0
@@ 1,29 @@
+# dgMux
+[](https://discord.gg/0f1SbxBZjYq9jLBk)
+<img align="right" src="https://raw.githubusercontent.com/wiki/bwmarrin/disgord/images/gourd.jpg">
+
+dgMux is a simple Discord message route multiplexer that parses a message and
+then executes a matching registered handler. dgMux can be used with
+[Disgord](https://github.com/bwmarrin/disgord) or the
+[DiscordGo](https://github.com/bwmarrin/discordgo) library.
+
+If you would like to help the Disgord or DiscordGo package please use
+[this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
+to add the official DiscordGo test bot **dgo** to your server. This provides
+indispensable help to this project.
+
+**For help with this program or general Go discussion, please join the [Discord
+Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.**
+
+**NOTE** : This is an experimental package and it's likely to have large changes
+breaking it's API and compatibility with previous versions.
+
+The goal with dgMux is to create a fairly straight forward and simple
+"command router" that can be added to any DiscordGo bot without much fuss. A
+secondary goal is to keep the route handlers as close to native DiscordGo
+handlers as possible. So that each example provided can be used by those
+just learning the DiscordGo API and easily integrated into their own projects.
+
+Some inspiration was taken from the [chi](https://github.com/go-chi/chi)
+and [httprouter](https://github.com/julienschmidt/httprouter) routers when
+creating dgMux.
A => x/mux/help.go +81 -0
@@ 1,81 @@
+package mux
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+
+ "github.com/bwmarrin/discordgo"
+)
+
+// Help function provides a build in "help" command that will display a list
+// of all registered routes (commands). To use this function it must first be
+// registered with the Mux.Route function.
+func (m *Mux) Help(ds *discordgo.Session, dm *discordgo.Message, ctx *Context) {
+
+ // Set command prefix to display.
+ cp := ""
+ if ctx.IsPrivate {
+ cp = ""
+ } else if ctx.HasPrefix {
+ cp = m.Prefix
+ } else {
+ cp = fmt.Sprintf("@%s ", ds.State.User.Username)
+ }
+
+ // Sort commands
+ maxlen := 0
+ keys := make([]string, 0, len(m.Routes))
+ cmdmap := make(map[string]*Route)
+
+ for _, v := range m.Routes {
+
+ // Only display commands with a description
+ if v.Description == "" {
+ continue
+ }
+
+ // Calculate the max length of command+args string
+ l := len(v.Pattern) // TODO: Add the +args part :)
+ if l > maxlen {
+ maxlen = l
+ }
+
+ cmdmap[v.Pattern] = v
+
+ // help and about are added separately below.
+ if v.Pattern == "help" || v.Pattern == "about" {
+ continue
+ }
+
+ keys = append(keys, v.Pattern)
+ }
+
+ sort.Strings(keys)
+
+ // TODO: Learn more link needs to be configurable
+ resp := "\n*Commands can be abbreviated and mixed with other text. Learn more at <https://github.com/bwmarrin/disgord>*\n"
+ resp += "```autoit\n"
+
+ v, ok := cmdmap["help"]
+ if ok {
+ keys = append([]string{v.Pattern}, keys...)
+ }
+
+ v, ok = cmdmap["about"]
+ if ok {
+ keys = append([]string{v.Pattern}, keys...)
+ }
+
+ // Add sorted result to help msg
+ for _, k := range keys {
+ v := cmdmap[k]
+ resp += fmt.Sprintf("%s%-"+strconv.Itoa(maxlen)+"s # %s\n", cp, v.Pattern+v.Help, v.Description)
+ }
+
+ resp += "```\n"
+
+ ds.ChannelMessageSend(dm.ChannelID, resp)
+
+ return
+}
A => x/mux/mux.go +202 -0
@@ 1,202 @@
+// Package mux provides a simple Discord message route multiplexer that
+// parses messages and then executes a matching registered handler, if found.
+// mux can be used with both Disgord and the DiscordGo library.
+package mux
+
+import (
+ "fmt"
+ "log"
+ "regexp"
+ "strings"
+
+ "github.com/bwmarrin/discordgo"
+)
+
+// Route holds information about a specific message route handler
+type Route struct {
+ Pattern string // match pattern that should trigger this route handler
+ Description string // short description of this route
+ Help string // detailed help string for this route
+ Run HandlerFunc // route handler function to call
+}
+
+// Context holds a bit of extra data we pass along to route handlers
+// This way processing some of this only needs to happen once.
+type Context struct {
+ Fields []string
+ Content string
+ GuildID string
+ IsDirected bool
+ IsPrivate bool
+ HasPrefix bool
+ HasMention bool
+ HasMentionFirst bool
+}
+
+// HandlerFunc is the function signature required for a message route handler.
+type HandlerFunc func(*discordgo.Session, *discordgo.Message, *Context)
+
+// Mux is the main struct for all mux methods.
+type Mux struct {
+ Routes []*Route
+ Default *Route
+ Prefix string
+}
+
+// New returns a new Discord message route mux
+func New() *Mux {
+ m := &Mux{}
+ m.Prefix = "-dg "
+ return m
+}
+
+// Route allows you to register a route
+func (m *Mux) Route(pattern, desc string, cb HandlerFunc) (*Route, error) {
+
+ r := Route{}
+ r.Pattern = pattern
+ r.Description = desc
+ r.Run = cb
+ m.Routes = append(m.Routes, &r)
+
+ return &r, nil
+}
+
+// FuzzyMatch attepts to find the best route match for a givin message.
+func (m *Mux) FuzzyMatch(msg string) (*Route, []string) {
+
+ // Tokenize the msg string into a slice of words
+ fields := strings.Fields(msg)
+
+ // no point to continue if there's no fields
+ if len(fields) == 0 {
+ return nil, nil
+ }
+
+ // Search though the command list for a match
+ var r *Route
+ var rank int
+
+ var fk int
+ for fk, fv := range fields {
+
+ for _, rv := range m.Routes {
+
+ // If we find an exact match, return that immediately.
+ if rv.Pattern == fv {
+ return rv, fields[fk:]
+ }
+
+ // Some "Fuzzy" searching...
+ if strings.HasPrefix(rv.Pattern, fv) {
+ if len(fv) > rank {
+ r = rv
+ rank = len(fv)
+ }
+ }
+ }
+ }
+ return r, fields[fk:]
+}
+
+// OnMessageCreate is a DiscordGo Event Handler function. This must be
+// registered using the DiscordGo.Session.AddHandler function. This function
+// will receive all Discord messages and parse them for matches to registered
+// routes.
+func (m *Mux) OnMessageCreate(ds *discordgo.Session, mc *discordgo.MessageCreate) {
+
+ var err error
+
+ // Ignore all messages created by the Bot account itself
+ if mc.Author.ID == ds.State.User.ID {
+ return
+ }
+
+ // Create Context struct that we can put various infos into
+ ctx := &Context{
+ Content: strings.TrimSpace(mc.Content),
+ }
+
+ // Fetch the channel for this Message
+ var c *discordgo.Channel
+ c, err = ds.State.Channel(mc.ChannelID)
+ if err != nil {
+ // Try fetching via REST API
+ c, err = ds.Channel(mc.ChannelID)
+ if err != nil {
+ log.Printf("unable to fetch Channel for Message,", err)
+ } else {
+ // Attempt to add this channel into our State
+ err = ds.State.ChannelAdd(c)
+ if err != nil {
+ log.Printf("error updating State with Channel,", err)
+ }
+ // Add Channel info into Context
+ ctx.GuildID = c.GuildID
+ if c.Type == discordgo.ChannelTypeDM {
+ ctx.IsPrivate = true
+ ctx.IsDirected = true
+ }
+ }
+ }
+
+ // Detect @name or @nick mentions
+ if !ctx.IsDirected {
+
+ // Detect if Bot was @mentioned
+ for _, v := range mc.Mentions {
+
+ if v.ID == ds.State.User.ID {
+
+ ctx.IsDirected, ctx.HasMention = true, true
+
+ reg := regexp.MustCompile(fmt.Sprintf("<@!?(%s)>", ds.State.User.ID))
+
+ // Was the @mention the first part of the string?
+ if reg.FindStringIndex(ctx.Content)[0] == 0 {
+ ctx.HasMentionFirst = true
+ }
+
+ // strip bot mention tags from content string
+ ctx.Content = reg.ReplaceAllString(ctx.Content, "")
+
+ break
+ }
+ }
+ }
+
+ // Detect prefix mention
+ if !ctx.IsDirected && len(m.Prefix) > 0 {
+
+ // TODO : Must be changed to support a per-guild user defined prefix
+ if strings.HasPrefix(ctx.Content, m.Prefix) {
+ ctx.IsDirected, ctx.HasPrefix, ctx.HasMentionFirst = true, true, true
+ ctx.Content = strings.TrimPrefix(ctx.Content, m.Prefix)
+ }
+ }
+
+ // For now, if we're not specifically mentioned we do nothing.
+ // later I might add an option for global non-mentioned command words
+ if !ctx.IsDirected {
+ return
+ }
+
+ // Try to find the "best match" command out of the message.
+ r, fl := m.FuzzyMatch(ctx.Content)
+ if r != nil {
+ ctx.Fields = fl
+ r.Run(ds, mc.Message, ctx)
+ return
+ }
+
+ // If no command match was found, call the default.
+ // Ignore if only @mentioned in the middle of a message
+ if m.Default != nil && (ctx.HasMentionFirst) {
+ // TODO: This could use a ratelimit
+ // or should the ratelimit be inside the cmd handler?..
+ // In the case of "talking" to another bot, this can create an endless
+ // loop. Probably most common in private messages.
+ m.Default.Run(ds, mc.Message, ctx)
+ }
+
+}