// Message receives a Message struct and sends it to appropriate channels func Message(s *discordgo.Session, m *types.Message) error { message := strings.Join(m.Payload, "\n") var channels []string var dchannels []*discordgo.Channel var err error c := config.Get() if m.Prefix != "" { message = fmt.Sprintf("%s: %s", m.Prefix, message) } if m.Channels[0] == "*" { dchannels, err = s.GuildChannels(c.Guild) if err != nil { return err } //errhandler.Handle(err) for _, chann := range dchannels { channels = append(channels, chann.ID) } } else { channels = m.Channels } log.Debugf("%s\n", len(channels)) for _, channel := range channels { s.ChannelMessageSend(channel, message) } return nil }
// Listen Tells Gin API to start func Listen(iface string, s *discordgo.Session, logger *logging.Logger) { // set the refs to point to main var v1 *gin.RouterGroup session = s c := config.Get() log = logger if c.LogLevel != "debug" { gin.SetMode(gin.ReleaseMode) } //r := gin.Default() r := gin.New() r.Use(loggerino()) r.Use(gin.Recovery()) if c.APIPassword != "" { log.Info("Basic Authentication enabled for API") v1 = r.Group("/v1", gin.BasicAuth(gin.Accounts{ c.APIUsername: c.APIPassword, })) } else { log.Warning("DIGO_API_PASSWORD and DIGO_API_USERNAME are not set") log.Warning("The API is open to all requests") v1 = r.Group("/v1") } v1.GET("/version", versionV1) v1.GET("/channels", channelsV1) v1.POST("/message", messageV1) go r.Run(iface) log.Noticef("Digo API is listening on %s", c.APIInterface) }
/* may be deprecated by session.Status in discordgo */ func pollConn(s *discordgo.Session) { c := config.Get() for { time.Sleep(10 * time.Second) found := false guilds, err := s.UserGuilds() for _, g := range guilds { if g.ID == c.Guild { found = true break } } if !found { log.Warningf("Could not find membership matching guild ID. %s", c.Guild) log.Warningf("Maybe I need a new invite? Using code %s", c.InviteID) if s.Token != "" { s.Close() } return } if err != nil { log.Warningf("Could not fetch guild info %v", err) if s.Token != "" { s.Close() } return } } }
// Register function builds the Plugins struct and calls "register" on each // plugin func Register() (found bool) { var pluginFiles []string var enabledPlugins []string var plugin *types.Plugin var err error c := config.Get() // build top level trigger cache p.AllTriggers = map[string]string{ c.Trigger: "__internal", } p.Directory, pluginFiles, err = searchPluginDir() log.Debug("Potential plugins: %v\n", pluginFiles) if err != nil { found = false log.Warning("Problem with plugins directory: %s\n", err) return } found = true for _, pluginName := range pluginFiles { plugin, err = registerPlugin(p.Directory, pluginName) if err != nil { log.Warningf("Could not register %s: %s\n", pluginName, err) } else { p.Plugins[pluginName] = plugin enabledPlugins = append(enabledPlugins, plugin.Name) } } log.Noticef("Enabled Plugins: %s", strings.Join(enabledPlugins, ", ")) return found }
func registerPlugin(dir string, file string) (plugin *types.Plugin, err error) { c := config.Get() // register string is hardcoded, always the first argument config, err := Exec(dir, file, []string{"register"}) err = json.Unmarshal(config, &plugin) if err != nil { err = fmt.Errorf("Couldn't run \"%s register\"\n", file) log.Errorf("%s\n", err) log.Debugf("%s\n", config) return } // default to simple type plugin if plugin.Type == "" { plugin.Type = "simple" } if plugin.Filename == "" { plugin.Filename = file } // input validation if len(plugin.Triggers) == 0 && len(plugin.Tokens) == 0 { err = fmt.Errorf("Plugin \"%s\" does nothing! It has no triggers or tokens. Not registering.", file) return } if plugin.Type == "simple" { log.Debugf("Simple plugin %s registered", plugin.Name) } else if plugin.Type == "json" { log.Debug("JSON plugin %s registered", plugin.Name) } else { log.Warningf("Plugin of unknown type registered: %s", plugin.Type) log.Warning("Valid types: simple, json") err = errors.New("Unknown plugin type") return } // trigger cache so we only have to iterate one set per message for _, trigger := range plugin.Triggers { // sorry, can't override /bot if trigger == c.Trigger { log.Info("Prevented plugin %s from trying to override bot trigger %s.", plugin.Name, c.Trigger) continue } p.AllTriggers[trigger] = file } // token cache so we only have to iterate one set per message for _, token := range plugin.Tokens { p.AllTokens[token] = file } //spew.Dump(config) return }
func acceptInvite(s *discordgo.Session) error { var err error c := config.Get() //time.Sleep(1 * time.Second) if c.InviteID != "" { log.Debugf("Attempting to accept invite: %s", c.InviteID) _, err = s.InviteAccept(c.InviteID) } else { log.Debug("No DIGO_INVITE_ID specified, no invite to accept.") } return err }
func channelsV1(c *gin.Context) { // this route is expensive since it's doing live fetching of channel information // expensive as in ~100ms var ch []*discordgo.Channel var err error config := config.Get() ch, err = session.GuildChannels(config.Guild) if err != nil { c.JSON(500, gin.H{ "message": "Could not fetch channel information", "error": fmt.Sprintf("%s", err), }) } else { c.JSON(200, ch) } }
func main() { var err error //p = plugins.Init() lock := make(chan int) // set up the config struct config.Init() // set the log reference to pass around log := logger.Init() errhandler.Init(log) // set up the plugins struct p := plugins.Init(log) //log.Notice() // handler takes reference to config and plugins structs handler.Init(p, log) // login / websocket flow s := conn.Init(log) // determine the bot's userID user, err := s.User("@me") errhandler.Handle(err) c := config.Get() c.UserID = user.ID // listen for events on Discord // conn.Listen(s, c, log) // enable the API, if applicable if c.DisableAPI { log.Notice("API explicitly disabled.") } else { go api.Listen(c.APIInterface, s, log) } log.Noticef("Digo v%s Online", globals.Version) <-lock }
func doLogin(s *discordgo.Session) error { var err error var token string c := config.Get() log.Debug("Logging in") token, err = s.Login(c.Email, c.Password) if err == nil { if token != "" { s.Token = token } } else { log.Errorf("Can't log in: %s", err) log.Error("Maybe your credentials are invalid?") } // since we're dealing with a ref, only return the error return err }
// MessageHandler is the callback for the discordgo session func MessageHandler(s *discordgo.Session, m *discordgo.Message) { var status int var command []string var handled bool c := config.Get() log.Infof("%s %s > %s", m.ChannelID, m.Author.Username, m.Content) // prevent the bot from triggering itself if m.Author.ID == c.UserID { return } // the /bot (or whatever) trigger always has precedence status, command = checkTrigger(c.Trigger, m.Content) handled = handleInternalCommand(status, command, s, m) if handled { return } // clean up the command // if status != globals.NOMATCH { // } for trigger, pluginFile := range p.AllTriggers { // grab the correct plugin from the Plugins struct plugin := p.Plugins[pluginFile] status, command = checkTrigger(trigger, m.Content) handled = handleExternalCommand(status, command, p.Directory, plugin, s, m) if handled { return } } }
func messageDelete(s *discordgo.Session, chanID string, mID string) { c := config.Get() if !c.KeepTriggers { s.ChannelMessageDelete(chanID, mID) } }
// Init sets up the Logger func Init() *logging.Logger { var log = logging.MustGetLogger("Digo") var llevel logging.Level var useStdout bool var useLogfile bool c := config.Get() // scoping made this a requirement // var logfileBackend *logging.LogBackend var logfileBackendFormatter logging.Backend var logfileBackendLeveled logging.LeveledBackend var stdoutBackend *logging.LogBackend var stdoutBackendFormatter logging.Backend var stdoutBackenedLeveled logging.LeveledBackend // // // log streams. ["stdout"] || ["file"] || ["stdout", "file"] for _, item := range strings.Split(c.LogStreams, ",") { switch { case item == "stdout": useStdout = true case item == "file": useLogfile = true } } llevel = setLoglevel(c.LogLevel, c) // If you want to completely suppress program output, redirect it to /dev/null if !useStdout && !useLogfile { fmt.Println("Explicitly enabling stdout") useStdout = true useLogfile = false // probably don't explicitly need this } if useLogfile { logfile, success := openLogfile(c.LogFile) if success { var logfileFormat = logging.MustStringFormatter( `%{time:15:04:05} %{shortfunc} > %{level:.4s} %{id:03x} %{message}`, ) logfileBackend = logging.NewLogBackend(logfile, "", 0) logfileBackendFormatter = logging.NewBackendFormatter(logfileBackend, logfileFormat) logfileBackendLeveled = logging.AddModuleLevel(logfileBackendFormatter) //logfileBackendLeveled.SetLevel(llevel, "Digo") logfileBackendLeveled.SetLevel(logging.DEBUG, "Digo") // log stuff } else { fmt.Println("Disabled logfile") useLogfile = false } } if useStdout { var stdoutFormat = logging.MustStringFormatter( `%{color}%{time:15:04:05} %{shortfunc} > %{level:.4s} %{id:03x}%{color:reset} %{message}`, ) stdoutBackend = logging.NewLogBackend(os.Stdout, "", 0) stdoutBackendFormatter = logging.NewBackendFormatter(stdoutBackend, stdoutFormat) stdoutBackenedLeveled = logging.AddModuleLevel(stdoutBackendFormatter) stdoutBackenedLeveled.SetLevel(llevel, "Digo") } switch { case useStdout && useLogfile: logging.SetBackend(stdoutBackenedLeveled, logfileBackendLeveled) case useStdout: logging.SetBackend(stdoutBackenedLeveled) case useLogfile: logging.SetBackend(logfileBackendLeveled) default: fmt.Println("Error: Could not enable any output.") os.Exit(2) } log.Debug("Logger initialized") return log }