--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/discord/commands.go Sat Apr 06 05:22:07 2019 -0500
@@ -0,0 +1,75 @@
+ "github.com/bwmarrin/discordgo" + "github.com/dustin/go-humanize" + log "github.com/sirupsen/logrus" + "bitbucket.org/TheToyz/nowdead/presence" +type commandHandler func(c *DiscordClient, m *discordgo.MessageCreate) error +var commands map[string]commandHandler + commands = map[string]commandHandler{ + "!uptime": uptimeCommand, + "!setchannel": setChannelCommand, + "!islive": isLiveCommand, +func (c *DiscordClient) messageHandler(s *discordgo.Session, m *discordgo.MessageCreate) { + // ignore messages for ourselves + if m.Author.ID == s.State.User.ID { + log.Debugf("processing message %v", m) + parts := strings.Split(m.Content, " ") + command := strings.ToLower(parts[0]) + if handler, found := commands[command]; found { + log.Warnf("failed to send response: %v", err) + c.sendChannel(m.ChannelID, fmt.Sprintf("error: %#v", err)) +func uptimeCommand(c *DiscordClient, m *discordgo.MessageCreate) error { + fmt.Sprintf("Started %s", humanize.Time(c.started)), +func setChannelCommand(c *DiscordClient, m *discordgo.MessageCreate) error { + c.db.SetChannel(m.GuildID, m.ChannelID) + fmt.Sprintf("Set <#%s> as the announcement channel", m.ChannelID), +func isLiveCommand(c *DiscordClient, m *discordgo.MessageCreate) error { + args := strings.Split(m.Content, " ") + return fmt.Errorf("invalid arguments") + presence, err := presence.GetPresence("https://twitch.tv/" + args[1]) + return c.sendPresence(m.GuildID, presence) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/discord/presence.go Sat Apr 06 05:22:07 2019 -0500
@@ -0,0 +1,74 @@
+ "github.com/bwmarrin/discordgo" + "github.com/dustin/go-humanize" + log "github.com/sirupsen/logrus" + "bitbucket.org/TheToyz/nowdead/presence" +func (c *DiscordClient) presenceHandler(s *discordgo.Session, p *discordgo.PresenceUpdate) { + log.Debugf("processing presence %v", p) + if p.Game.Type != discordgo.GameTypeStreaming { + presence, _ := presence.GetPresence(p.Game.URL) + if err := c.sendPresence(p.GuildID, presence); err != nil { + log.Warnf("failed to send presence to guild %s: %v", p.GuildID, err) +func (c *DiscordClient) sendPresence(guild string, presence presence.Presence) error { + channelID := c.db.GetChannel(guild) + return errors.New("No channel set") + return c.sendPresenceChannel(channelID, presence) +func (c *DiscordClient) sendPresenceChannel(channelID string, presence presence.Presence) error { + _, err := c.client.ChannelMessageSendEmbed( + &discordgo.MessageEmbed{ + Image: &discordgo.MessageEmbedImage{ + URL: presence.ProfileImageURL, + Author: &discordgo.MessageEmbedAuthor{ + Name: fmt.Sprintf("%s is now live", presence.Username), + Thumbnail: &discordgo.MessageEmbedThumbnail{ + URL: presence.ThumbnailURL, + Fields: []*discordgo.MessageEmbedField{ + &discordgo.MessageEmbedField{ + &discordgo.MessageEmbedField{ + Value: humanize.Comma(presence.Viewers), + &discordgo.MessageEmbedField{ + Value: presence.Language, --- a/go.mod Sat Apr 06 00:39:10 2019 -0700
+++ b/go.mod Sat Apr 06 05:22:07 2019 -0500
@@ -5,7 +5,8 @@
github.com/bwmarrin/discordgo v0.19.0
github.com/dustin/go-humanize v1.0.0
- github.com/go-redis/redis v6.15.2+incompatible // indirect
+ github.com/go-redis/redis v6.15.2+incompatible github.com/nicklaw5/helix v0.5.1
github.com/prologic/bitcask v0.1.6
+ github.com/sirupsen/logrus v1.4.0 --- a/go.sum Sat Apr 06 00:39:10 2019 -0700
+++ b/go.sum Sat Apr 06 05:22:07 2019 -0500
@@ -47,6 +47,7 @@
github.com/prologic/trie v0.0.0-20190322091023-3972df81f9b5 h1:H8dTZzU3aWNQnuRyiT45J9szv7EFakAhFzsFq27t3Uo=
github.com/prologic/trie v0.0.0-20190322091023-3972df81f9b5/go.mod h1:LFuDmpHJGmciXd8Rl5YMhVlLMps9gz2GtYLzwxrFhzs=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -85,6 +86,7 @@
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
--- a/main.go Sat Apr 06 00:39:10 2019 -0700
+++ b/main.go Sat Apr 06 05:22:07 2019 -0500
@@ -1,39 +1,33 @@
- "bitbucket.org/TheToyz/nowdead/database"
+ log "github.com/sirupsen/logrus" - "github.com/dustin/go-humanize"
- "github.com/bwmarrin/discordgo"
- "github.com/nicklaw5/helix"
+ "bitbucket.org/TheToyz/nowdead/database" + "bitbucket.org/TheToyz/nowdead/discord" + "bitbucket.org/TheToyz/nowdead/presence" + "bitbucket.org/TheToyz/nowdead/twitch" - TwitchClient *helix.Client
- logger = log.New(os.Stderr, " ", log.Ldate|log.Ltime)
+ log.SetOutput(os.Stdout) + log.SetLevel(log.DebugLevel) flag.StringVar(&DiscordToken, "t", "", "Discord Bot Token")
flag.StringVar(&TwitchToken, "tcid", "", "Twitch Client ID")
@@ -42,209 +36,53 @@
-func logDebug(v ...interface{}) {
- logger.SetPrefix("DEBUG ")
-func logInfo(v ...interface{}) {
- logger.SetPrefix("INFO ")
-func logError(v ...interface{}) {
- logger.SetPrefix("ERROR ")
-func logPanic(v ...interface{}) {
- logger.SetPrefix("PANIC ")
db = database.Get(DatabaseType)
if len(DiscordToken) <= 0 || len(TwitchToken) <= 0 {
- logPanic("Discord Token and Twitch Client ID must be set")
+ log.Panic("Discord Token and Twitch Client ID must be set") - logDebug("Discord Token:", DiscordToken)
- logDebug("Twitch Token:", TwitchToken)
+ if len(TwitchToken) > 0 { + provider, err := twitch.New(TwitchToken) - client, err := helix.NewClient(&helix.Options{
+ presence.AddProvider("twitch.tv", provider) + log.Info("Added twitch provider")
- dg, err := discordgo.New("Bot " + strings.TrimSpace(DiscordToken))
+ // create our error channel that's used by the various clients + errChan := make(chan error, 1) + // create the discord client + dc, err := discord.New(DiscordToken, db) fmt.Println("error creating Discord session,", err)
- dg.AddHandler(presenceUpdate)
- dg.AddHandler(messageCreate)
+ log.Info("Connected to discord") - fmt.Println("Bot is now running. Press CTRL-C to exit.")
- sc := make(chan os.Signal, 1)
- signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
-func presenceUpdate(sess *discordgo.Session, evt *discordgo.PresenceUpdate) {
- logDebug("PRESENSE UPDATE user-ID:", evt.User.ID)
- logDebug(fmt.Sprintf("PRESENSE UPDATE game: %#v", evt.Game))
+ // create a channel for handling unix signals + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) - if evt.Game.Type == discordgo.GameTypeStreaming {
- pURL, _ := url.Parse(evt.Game.URL)
- channel := db.GetChannel(evt.GuildID)
- err := processPresenceUpdate(sess, channel, strings.TrimLeft(pURL.Path, "/"), evt.Game.URL)
-func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
- if m.Author.ID == s.State.User.ID {
- commandItems := strings.Split(m.Content, " ")
- if strings.EqualFold(commandItems[0], "!uptime") {
- duration := time.Now().Sub(startTime)
- channel := db.GetChannel(m.GuildID)
- if channel != m.ChannelID {
+ log.Info("Bot is now running. Press CTRL-C to exit.") - err := sendMessage(s, channel, fmt.Sprintf(
- "Uptime is: **%02d:%02d:%02d** (since **%s**)",
- int(duration.Minutes())%60,
- int(duration.Seconds())%60,
- startTime.Format(time.Stamp)))
- if strings.EqualFold(commandItems[0], "!isLive") {
- if len(commandItems) == 1 {
+ case s := <-signalChan: + log.Infof("caught %s Exiting...", s) - channel := db.GetChannel(m.GuildID)
- if channel != m.ChannelID {
- err := processPresenceUpdate(s, channel, commandItems[1], "https://twitch.com/"+commandItems[1])
- sendMessage(s, channel, fmt.Sprintf("_%s_ does not seem to be live right now!", commandItems[1]))
- if strings.EqualFold(commandItems[0], "!init") {
- sendMessage(s, m.ChannelID, "All set up to talk in this channel!")
- db.SetChannel(m.GuildID, m.ChannelID)
-func sendMessage(sess *discordgo.Session, channelid, message string) error {
- logInfo("SENDING MESSAGE:", channelid, message)
- _, err := sess.ChannelMessageSend(channelid, message)
-func processPresenceUpdate(s *discordgo.Session, channelid, twitchName, url string) error {
- resp, err := TwitchClient.GetUsers(&helix.UsersParams{
- Logins: []string{twitchName},
- data, _ := TwitchClient.GetStreams(&helix.StreamsParams{
- UserLogins: []string{twitchName},
- if len(resp.Data.Users) <= 0 || len(data.Data.Streams) <= 0 {
- return errors.New("user was not found")
- user := resp.Data.Users[0]
- stream := data.Data.Streams[0]
- f := make([]*discordgo.MessageEmbedField, 0)
- f = append(f, &discordgo.MessageEmbedField{
- f = append(f, &discordgo.MessageEmbedField{
- Value: humanize.Comma(int64(stream.ViewerCount)),
- f = append(f, &discordgo.MessageEmbedField{
- Value: stream.Language,
- image := strings.Replace(stream.ThumbnailURL, "{width}", "400", -1)
- image = strings.Replace(image, "{height}", "225", -1)
- _, err = s.ChannelMessageSendEmbed(channelid, &discordgo.MessageEmbed{
- Image: &discordgo.MessageEmbedImage{
- Author: &discordgo.MessageEmbedAuthor{
- Name: user.DisplayName + " is now live",
- Thumbnail: &discordgo.MessageEmbedThumbnail{
- URL: user.ProfileImageURL,
-func fileExist(name string) bool {
- if _, err := os.Stat(name); err != nil {
- if os.IsNotExist(err) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/presence/presence.go Sat Apr 06 05:22:07 2019 -0500
@@ -0,0 +1,29 @@
+type Provider interface { + GetPresence(url string) (Presence, error) +var providers map[string]Provider + providers = map[string]Provider{} +func AddProvider(hostname string, provider Provider) { + providers[hostname] = provider +func GetPresence(url string) (Presence, error) { + return providers["twitch.tv"].GetPresence(url)