Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Youtube Bee (#25) #248

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6a4b478
initial verson done
Mark-Jung Apr 30, 2019
bd37889
error messages
Mark-Jung Apr 30, 2019
0529378
done with youtube bee;
Mark-Jung May 14, 2019
6904446
Added logs API end-point, which lets you retrieve Beehive's logs
muesli Apr 29, 2019
095472f
Reverse ordering in LogSorter
muesli Apr 29, 2019
022293f
Sideload Hives in API requests to /bees
muesli Apr 29, 2019
6511f38
Bees can now expose their state
muesli Apr 29, 2019
2d82826
Support states in IRCBee
muesli Apr 29, 2019
a896302
Fixed converting strong to float64 in ConvertValue
muesli Apr 30, 2019
20081f0
Go mod tidy our module deps
muesli May 1, 2019
d9e2b63
Bumped dependencies to latest compatible releases
muesli May 1, 2019
ac93554
Added Prometheus Bee (#231)
CalmBit May 1, 2019
7960d01
Updated Go module definitions
muesli May 1, 2019
7d52298
fix handling of boolean config values
mkrauser May 2, 2019
1c9af59
Cronbee fix
CalmBit May 3, 2019
714db01
LogDebugF
CalmBit May 3, 2019
f15c349
TravisCI Bee (#234)
CalmBit May 4, 2019
fc99c8f
added email server bee
mkrauser May 2, 2019
6184238
Bumped smolder dependency to @master
muesli May 5, 2019
9032dea
Add timestamp to telegram events (#153)
rubiojr May 6, 2019
abc4184
Added missing event name
mkrauser May 6, 2019
f0a1a85
Add JSON-Function for Templates (#242)
mkrauser May 7, 2019
9137dbb
Added Docker example with CANONICAL_URL env var
muesli May 7, 2019
7715804
Discord Bee (#239)
CalmBit May 8, 2019
d28e8b2
Disable notification & serial hives on non-Linux unices
muesli May 9, 2019
7b75d90
Fixed typos in a few bees
muesli May 13, 2019
4398116
fixed comments
Mark-Jung May 15, 2019
def2f42
forgot to fix indentation. oops.
Mark-Jung May 15, 2019
92b4601
Merge branch 'youtube-bee' of github.com:Mark-Jung/beehive into youtu…
Mark-Jung May 23, 2019
e1a471f
debugging
Mark-Jung May 26, 2019
2e70d41
Merge branch 'master' into youtube-bee
Mark-Jung May 26, 2019
a5a1018
specified incoming events to two
Mark-Jung May 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions bees/youtubebee/youtube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 Daniel 'grindhold' Brendle
* 2014-2017 Christian Muehlhaeuser
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Daniel 'grindhold' Brendle <grindhold@skarphed.org>
CalmBit marked this conversation as resolved.
Show resolved Hide resolved
* Christian Muehlhaeuser <muesli@gmail.com>
* Mark Jung <gujung2022@u.northwestern.edu>
*/

// Package youtubebee is a Bee for tunneling Youtube push notifications.
package youtubebee

import (
"time"
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/muesli/beehive/bees"
)

// YoutubeBee is a Bee for handling Youtube push notifications.
type YoutubeBee struct {
bees.Bee

url string

addr string

eventChan chan bees.Event
}

type Feed struct {
XMLName xml.Name `xml:"feed"`
Text string `xml:",chardata"`
Yt string `xml:"yt,attr"`
Xmlns string `xml:"xmlns,attr"`
Link []struct {
Text string `xml:",chardata"`
Rel string `xml:"rel,attr"`
Href string `xml:"href,attr"`
} `xml:"link"`
Title string `xml:"title"`
Updated string `xml:"updated"`
Entry struct {
Text string `xml:",chardata"`
ID string `xml:"id"`
VideoId string `xml:"videoId"`
ChannelId string `xml:"channelId"`
Title string `xml:"title"`
Link struct {
Text string `xml:",chardata"`
Rel string `xml:"rel,attr"`
Href string `xml:"href,attr"`
} `xml:"link"`
Author struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
URI string `xml:"uri"`
} `xml:"author"`
Published string `xml:"published"`
Updated string `xml:"updated"`
} `xml:"entry"`
}

// Run executes the Bee's event loop.
func (mod *YoutubeBee) Run(eventChan chan bees.Event) {
mod.eventChan = eventChan
subscriptionLink := "https://pubsubhubbub.appspot.com/subscribe"
channelURLTokens := strings.Split(mod.url, "/")
channelID := channelURLTokens[len(channelURLTokens)-1]
topic := "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + channelID

srv := &http.Server{Addr: mod.addr, Handler: mod}
l, err := net.Listen("tcp", mod.addr)
if err != nil {
mod.LogErrorf("Can't listen on %s", mod.addr)
return
}
defer l.Close()

go func() {
err := srv.Serve(l)
if err != nil {
mod.LogErrorf("Server error: %v", err)
}
// Go 1.8+: srv.Close()
// send POST to Google's pubsubhubbub to subscribe
// need to be in form-data format
data := url.Values{}
data.Set("hub.mode", "subscribe")
data.Set("hub.topic", topic)
data.Set("hub.callback", "mod.addr")
client := &http.Client{}
r, _ := http.NewRequest("POST", subscriptionLink, strings.NewReader(data.Encode())) // URL-encoded payload
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

_, err = client.Do(r)
if err != nil {
mod.LogErrorf("Can't subscribe to youtube channel")
return
}
}()

select {
case <-mod.SigChan:
return
}
}

func (mod *YoutubeBee) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method == "POST" {
ev := bees.Event{
Bee: mod.Name(),
}
var feed Feed
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
}
xml.Unmarshal([]byte(body), &feed)
updatedTime, _ := time.Parse(time.RFC3339, feed.Entry.Updated)
publishedTime, _ := time.Parse(time.RFC3339, feed.Entry.Published)
diff := updatedTime.Sub(publishedTime)
// give the channel a minute window - for new videos, update and publish times aren't exact.
if diff.Minutes() <= 5 {
ev.Name = "new_video"
} else {
ev.Name = "change_video"
}
ev.Options.SetValue("channelUrl", "string", feed.Entry.Author.URI)
ev.Options.SetValue("vidUrl", "string", feed.Entry.Link.Href)

mod.eventChan <- ev
} else if req.Method == "GET" {
challenge := req.URL.Query().Get("hub.challenge")
if challenge != "" {
fmt.Fprintf(w, challenge)
}
}
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *YoutubeBee) ReloadOptions(options bees.BeeOptions) {
mod.SetOptions(options)

options.Bind("address", &mod.addr)
options.Bind("channel", &mod.url)
}
131 changes: 131 additions & 0 deletions bees/youtubebee/youtubefactory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (C) 2014-2017 Christian Muehlhaeuser
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Christian Muehlhaeuser <muesli@gmail.com>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add yourself here too! 😄

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha yup.

* Mark Jung <gujung2022@u.northwestern.edu>
*/

package youtubebee

import (
"github.com/muesli/beehive/bees"
)

// WebBeeFactory is a factory for WebBees.
type YoutubeBeeFactory struct {
bees.BeeFactory
}

// New returns a new Bee instance configured with the supplied options.
func (factory *YoutubeBeeFactory) New(name, description string, options bees.BeeOptions) bees.BeeInterface {
bee := YoutubeBee{
Bee: bees.NewBee(name, factory.ID(), description, options),
}
bee.ReloadOptions(options)

return &bee
}

// ID returns the ID of this Bee.
func (factory *YoutubeBeeFactory) ID() string {
return "youtubebee"
}

// Name returns the name of this Bee.
func (factory *YoutubeBeeFactory) Name() string {
return "Youtube"
}

// Description returns the description of this Bee.
func (factory *YoutubeBeeFactory) Description() string {
return "HTTP Server that listens to a Youtube channel"
}

// Image returns the filename of an image for this Bee.
func (factory *YoutubeBeeFactory) Image() string {
return factory.ID() + ".png"
}

// LogoColor returns the preferred logo background color (used by the admin interface).
func (factory *YoutubeBeeFactory) LogoColor() string {
return "#ff0000"
}

// Options returns the options available to configure this Bee.
func (factory *YoutubeBeeFactory) Options() []bees.BeeOptionDescriptor {
opts := []bees.BeeOptionDescriptor{
{
Name: "address",
Description: "Which addr to listen on, eg: 0.0.0.0:12345",
Type: "address",
Mandatory: true,
},
{
Name: "channel",
Description: "What is the link of the channel you want to receive push notifications for?",
Type: "url",
Mandatory: true,
},
}
return opts
}

// Events describes the available events provided by this Bee.
func (factory *YoutubeBeeFactory) Events() []bees.EventDescriptor {
events := []bees.EventDescriptor{
{
Namespace: factory.Name(),
Name: "new_video",
Description: "The channel posted a new video",
Options: []bees.PlaceholderDescriptor{
{
Name: "channelUrl",
Description: "The url of the channel push notification was sent from",
Type: "url",
},
{
Name: "vidUrl",
Description: "The url of the video relevant to the push notification",
Type: "url",
},
},
},
{
Namespace: factory.Name(),
Name: "change_video",
Description: "The channel updated a video",
Options: []bees.PlaceholderDescriptor{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any other information that can be extracted from YouTube's API? As it stands, this mostly just seems like a generic push, rather than something more actionable. If not, that's absolutely fine - I'm just wondering, so we can broaden it now if possible 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, again, if we can extract more information, then @muesli's suggestion of dividing the current monolith event into multiple events might be a good idea.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree how the current monolith event should be split into multiple events so I looked at the notification and the response object provided in the documentation. Unfortunately, I don't think it's possible to divide the notification into three different types of events. I think you can definitely expand this bee for more functionalities such as uploading video, but in the aspect of notification this is as specific as it can get I believe.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the publication/updated date are initially set to the same value for initial publication, but if they were you could check if they were the same, and at least divide them into two - the initial publish and the update.

Copy link
Author

@Mark-Jung Mark-Jung May 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof good catch. I missed that. Thank you for your insight. I'll commit that this weekend!

{
Name: "channelUrl",
Description: "The url of the channel push notification was sent from",
Type: "url",
},
{
Name: "vidUrl",
Description: "The url of the video relevant to the push notification",
Type: "url",
},
},
},
}
return events
}

func init() {
f := YoutubeBeeFactory{}
bees.RegisterFactory(&f)
}
1 change: 1 addition & 0 deletions hives.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ import (
_ "github.com/muesli/beehive/bees/twiliobee"
_ "github.com/muesli/beehive/bees/twitterbee"
_ "github.com/muesli/beehive/bees/webbee"
_ "github.com/muesli/beehive/bees/youtubebee"
)