grim/wasdead

initial start of youtube support
draft
2020-01-21, Gary Kramlich
d86e524fcac6
Parents ca7cc4194053
Children ad5629a767e1
initial start of youtube support
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/consts/consts.go Tue Jan 21 21:10:24 2020 -0600
@@ -0,0 +1,6 @@
+package consts
+
+const (
+ WasDeadURL = "https://bitbucket.org/rw_grim/wasdead/"
+ WasDeadImageURL = ""
+)
--- a/discord/cmdtwitch.go Sun Nov 03 03:29:23 2019 -0600
+++ b/discord/cmdtwitch.go Tue Jan 21 21:10:24 2020 -0600
@@ -1,6 +1,8 @@
package discord
import (
+ "fmt"
+
"bitbucket.org/rw_grim/wasdead/presence"
)
@@ -9,9 +11,12 @@
}
func (c *TwitchCmd) Run(g *Globals) error {
- uri := "https://twitch.tv/" + c.Target
+ provider := presence.GetProvider("twitch")
+ if provider == nil {
+ return fmt.Errorf("the twitch provider is not configured")
+ }
- presence, err := presence.GetPresence(uri)
+ presence, err := provider.GetPresenceFromUsername(c.Target)
if err != nil {
return err
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/discord/cmdyoutube.go Tue Jan 21 21:10:24 2020 -0600
@@ -0,0 +1,25 @@
+package discord
+
+import (
+ "fmt"
+
+ "bitbucket.org/rw_grim/wasdead/presence"
+)
+
+type YoutubeCmd struct {
+ Target string `kong:"arg"`
+}
+
+func (c *YoutubeCmd) Run(g *Globals) error {
+ provider := presence.GetProvider("youtube")
+ if provider == nil {
+ return fmt.Errorf("the youtube provider is not configured")
+ }
+
+ presence, err := provider.GetPresenceFromUsername(c.Target)
+ if err != nil {
+ return err
+ }
+
+ return g.client.sendEmbedChannel(g.msg.ChannelID, presenceEmbed(presence))
+}
--- a/discord/commands.go Sun Nov 03 03:29:23 2019 -0600
+++ b/discord/commands.go Tue Jan 21 21:10:24 2020 -0600
@@ -24,6 +24,7 @@
Status StatusCmd `kong:"cmd,help='get the streaming status of someone'"`
Twitch TwitchCmd `kong:"cmd,help='check if someone is streaming on twitch'"`
Uptime UptimeCmd `kong:"cmd,help='display how long the bot has been running for'"`
+ Youtube YoutubeCmd `kong:"cmd,help='check if someone is streaming on youtube'"`
}
type Order66Cmd struct{}
--- a/discord/presence.go Sun Nov 03 03:29:23 2019 -0600
+++ b/discord/presence.go Tue Jan 21 21:10:24 2020 -0600
@@ -7,6 +7,7 @@
"github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
+ "bitbucket.org/rw_grim/wasdead/consts"
"bitbucket.org/rw_grim/wasdead/database"
"bitbucket.org/rw_grim/wasdead/presence"
)
@@ -52,22 +53,32 @@
}
func presenceEmbed(presence presence.Presence) *discordgo.MessageEmbed {
- return &discordgo.MessageEmbed{
- Image: &discordgo.MessageEmbedImage{
- URL: presence.ProfileImageURL,
+ embed := &discordgo.MessageEmbed{
+ Author: &discordgo.MessageEmbedAuthor{
+ Name: "WasDead",
+ URL: consts.WasDeadURL,
},
- Author: &discordgo.MessageEmbedAuthor{
- URL: presence.URL,
- Name: fmt.Sprintf("%s is now live", presence.Username),
- },
+ Description: "description",
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: presence.ThumbnailURL,
},
- Fields: []*discordgo.MessageEmbedField{
- &discordgo.MessageEmbedField{
- Name: "Title",
- Value: presence.Title,
- },
+ Title: presence.Title,
+ URL: presence.URL,
+ }
+
+ if presence.Color != 0 {
+ embed.Color = presence.Color
+ }
+
+ if presence.Live {
+ embed.Title = fmt.Sprintf("%s is live", presence.Username)
+
+ embed.Image = &discordgo.MessageEmbedImage{
+ URL: presence.ProfileImageURL,
+ }
+
+ embed.Fields = append(
+ embed.Fields,
&discordgo.MessageEmbedField{
Name: "Viewers",
Value: humanize.Comma(presence.Viewers),
@@ -78,6 +89,8 @@
Value: presence.Language,
Inline: true,
},
- },
+ )
}
+
+ return embed
}
--- a/presence/presence.go Sun Nov 03 03:29:23 2019 -0600
+++ b/presence/presence.go Tue Jan 21 21:10:24 2020 -0600
@@ -1,6 +1,13 @@
package presence
+import (
+ "fmt"
+)
+
type Presence struct {
+ Provider string
+ Color int
+ Live bool
StreamID string
Username string
UserID string
@@ -12,20 +19,13 @@
ThumbnailURL string
}
-type Provider interface {
- GetPresence(url string) (Presence, error)
-}
+// GetPresence returns the presence for the given url
+func GetPresence(url string) (Presence, error) {
+ for _, provider := range providers {
+ if provider.HandleURL(url) {
+ return provider.GetPresenceFromURL(url)
+ }
+ }
-var providers map[string]Provider
-
-func init() {
- providers = map[string]Provider{}
+ return Presence{}, fmt.Errorf("no provider for %q", url)
}
-
-func AddProvider(hostname string, provider Provider) {
- providers[hostname] = provider
-}
-
-func GetPresence(url string) (Presence, error) {
- return providers["twitch.tv"].GetPresence(url)
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/presence/provider.go Tue Jan 21 21:10:24 2020 -0600
@@ -0,0 +1,35 @@
+package presence
+
+type Provider interface {
+ HandleURL(url string) bool
+ GetPresenceFromURL(url string) (Presence, error)
+ GetPresenceFromUsername(username string) (Presence, error)
+}
+
+var providers map[string]Provider
+
+func init() {
+ providers = map[string]Provider{}
+}
+
+// AddProvider adds the given provider with the given name
+func AddProvider(name string, provider Provider) {
+ providers[name] = provider
+}
+
+// NumProviders returns how many presence providers have been configured
+func NumProviders() int {
+ return len(providers)
+}
+
+// ProviderAvailable returns whether or not a
+func ProviderAvailable(name string) bool {
+ _, found := providers[name]
+
+ return found
+}
+
+// GetProvider returns the provider with the given name or nil
+func GetProvider(name string) Provider {
+ return providers[name]
+}
--- a/run/run.go Sun Nov 03 03:29:23 2019 -0600
+++ b/run/run.go Tue Jan 21 21:10:24 2020 -0600
@@ -13,11 +13,13 @@
"bitbucket.org/rw_grim/wasdead/globals"
"bitbucket.org/rw_grim/wasdead/presence"
"bitbucket.org/rw_grim/wasdead/twitch"
+ "bitbucket.org/rw_grim/wasdead/youtube"
)
type RunCmd struct {
DiscordToken string `kong:"flag,name='discord-token',env='DISCORD_TOKEN',help='The bot token for discord',required"`
- TwitchClientID string `kong:"flag,name='twitch-client-id',env='TWITCH_CLIENT_ID',help='The Twitch client ID',required"`
+ TwitchClientID string `kong:"flag,name='twitch-client-id',env='TWITCH_CLIENT_ID',help='The Twitch client ID',optional"`
+ YouTubeApiKey string `kong:"flag,name='youtube-api-key',env='YOUTUBE_API_KEY',help='The Youtube api key',optional"`
}
var (
@@ -41,10 +43,24 @@
log.Panic(err)
}
- presence.AddProvider("twitch.tv", provider)
+ presence.AddProvider("twitch", provider)
log.Info("Added twitch provider")
}
+ if len(r.YouTubeApiKey) > 0 {
+ provider, err := youtube.New(r.YouTubeApiKey)
+ if err != nil {
+ log.Panic(err)
+ }
+
+ presence.AddProvider("youtube", provider)
+ log.Info("Added youtube provider")
+ }
+
+ if presence.NumProviders() == 0 {
+ log.Panic("no presence providers configured!")
+ }
+
// create our error channel that's used by the various clients
errChan := make(chan error, 1)
--- a/twitch/twitch.go Sun Nov 03 03:29:23 2019 -0600
+++ b/twitch/twitch.go Tue Jan 21 21:10:24 2020 -0600
@@ -25,10 +25,26 @@
return &Twitch{client: client}, nil
}
-func (t *Twitch) GetPresence(uri string) (presence.Presence, error) {
+func (t *Twitch) HandleURL(uri string) bool {
+ parsedURL, _ := url.Parse(uri)
+
+ for _, host := range []string{"twitch.tv", "www.twitch.tv"} {
+ if parsedURL.Host == host {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (t *Twitch) GetPresenceFromURL(uri string) (presence.Presence, error) {
parsedURL, _ := url.Parse(uri)
username := strings.TrimLeft(parsedURL.Path, "/")
+ return t.GetPresenceFromUsername(username)
+}
+
+func (t *Twitch) GetPresenceFromUsername(username string) (presence.Presence, error) {
users, err := t.client.GetUsers(&helix.UsersParams{
Logins: []string{username},
})
@@ -62,7 +78,7 @@
Language: stream.Language,
ProfileImageURL: profile_url,
ThumbnailURL: user.ProfileImageURL,
- URL: uri,
+ URL: "https://twitch.tv/" + username,
}
return p, nil
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/youtube/youtube.go Tue Jan 21 21:10:24 2020 -0600
@@ -0,0 +1,109 @@
+package youtube
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "strings"
+
+ "google.golang.org/api/option"
+ "google.golang.org/api/youtube/v3"
+
+ "bitbucket.org/rw_grim/wasdead/presence"
+)
+
+type YouTube struct {
+ service *youtube.Service
+}
+
+func New(apiKey string) (presence.Provider, error) {
+ ctx := context.Background()
+ service, err := youtube.NewService(ctx, option.WithAPIKey(apiKey))
+ if err != nil {
+ return nil, err
+ }
+
+ return &YouTube{service: service}, nil
+}
+
+func (yt *YouTube) HandleURL(uri string) bool {
+ parsed, _ := url.Parse(uri)
+
+ for _, host := range []string{"youtube.com", "youtu.be", "www.youtube.com"} {
+ if parsed.Host == host {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (yt *YouTube) getChannel(username string) (*youtube.SearchResultSnippet, error) {
+ call := yt.service.Search.List("id,snippet").
+ Q(username).
+ Type("channel").
+ ChannelType("any").
+ Order("relevance").
+ MaxResults(1)
+
+ resp, err := call.Do()
+ if err != nil {
+ return nil, err
+ }
+
+ if len(resp.Items) == 0 {
+ return nil, fmt.Errorf("no channels found matching %q", username)
+ }
+
+ return resp.Items[0].Snippet, nil
+}
+
+func (yt *YouTube) getLiveStreamID(channelID string) (string, error) {
+ call := yt.service.Search.List("id,snippet").
+ Type("video").
+ EventType("live").
+ MaxResults(1)
+
+ resp, err := call.Do()
+ if err != nil {
+ return "", err
+ }
+
+ if len(resp.Items) == 0 {
+ return "", nil
+ }
+
+ return resp.Items[0].Id.VideoId, nil
+}
+
+func (yt *YouTube) GetPresenceFromUsername(username string) (presence.Presence, error) {
+ p := presence.Presence{}
+
+ chanSnippet, err := yt.getChannel(username)
+ if err != nil {
+ return p, err
+ }
+
+ liveStreamID, err := yt.getLiveStreamID(chanSnippet.ChannelId)
+ if err != nil {
+ return p, err
+ }
+
+ // fill out the presence structure
+ p.Color = 0xc4302b
+ p.UserID = chanSnippet.ChannelId
+ p.Title = chanSnippet.Title + " : " + liveStreamID
+ p.ThumbnailURL = chanSnippet.Thumbnails.Default.Url
+ p.ProfileImageURL = chanSnippet.Thumbnails.High.Url
+ p.Viewers = 0
+ p.Language = "en"
+
+ return p, nil
+}
+
+func (yt *YouTube) GetPresenceFromURL(uri string) (presence.Presence, error) {
+ parsedURL, _ := url.Parse(uri)
+ username := strings.TrimLeft(parsedURL.Path, "/")
+
+ return yt.GetPresenceFromUsername(username)
+}