Introduction
Google came up with a notification service which is known as PubSubHubBub
. This system is used for giving prompt notification on any updates on a website. This article is about getting notification from youtube when a youtuber uploads video.
Background
Getting notification requires 3 major steps to be followed:
- Subscribing to Channel
- Receiving Subscription Confirmation
- Receiving Upload Notification
You can find the information at Youtube Data API Documentation.
Information is given in a straight forward manner. But in reality, setting up a web service is a bit tricky.
Test and find channel subscription details here.
Using the Code
Following are the steps for a complete Notification Service.
1. Channel Subscription
For subscribing to a channel, a web request must be created to "subscribeUrl
".
The most important parameters to be sent are:
hub.mode
= subscribe/unsubscribe hub.verify_token
= Unique Identifier, which can be used as a verification when youtube sends notification hub.verify
= sync/async (Recommended async, as sync seems bit problematic) hub.callback
= URL where youtube will send response to, P.S. localhost is not supported, needs to be deployed to a server hub.topic
= Youtube channel notification URL with channelId in the query string
[HttpGet]
public void Subscribe (string channelId) {
try {
var callBackUrl = "YOUR CALL BACK URL HERE";
var topicUrl = $"https://www.youtube.com/xml/feeds/videos.xml?channel_id={channelId}";
var subscribeUrl = "https://pubsubhubbub.appspot.com/subscribe";
string postDataStr = $"hub.mode=subscribe&hub.verify_token={Guid.NewGuid().ToString()}&
hub.verify=async&hub.callback={HttpUtility.UrlEncode(callBackUrl)}&
hub.topic={HttpUtility.UrlEncode(topicUrl)}";
byte[] postData = Encoding.UTF8.GetBytes(postDataStr);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(subscribeUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postData.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(postData, 0, postData.Length);
requestStream.Flush();
requestStream.Close();
HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
if (request.HaveResponse)
{
Stream responseStream = webResponse.GetResponseStream();
StreamReader responseReader = new StreamReader(responseStream, Encoding.UTF8);
responseReader.ReadToEnd();
}
else
{
throw new Exception("Didn't receive any response from the hub");
}
}
catch (Exception ex){
throw new Exception($"Error publishing Channel ID : {channelId}", ex);
}
}
2. Receive Subscription Confirmation & Youtube Notification
For receiving confirmation and notification, same API callback URL is used. The tricky part is, when receiving subscription confirmation, the call method used is "GET
" and for receiving Youtube notification it is "POST
".
Due to which the method should have both GET
and POST
verbs. Removing one would result in a 409 error.
For notification, an Atom Feed is used for PUSH notification.
Create a YoutubePush
Class to set Atom Feed:
public class YoutubeNotification
{
public string Id { get; set; }
public string VideoId { get; set; }
public string ChannelId { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public Author Author { get; set; }
public string Published { get; set; }
public string Updated { get; set; }
public bool IsNewVideo
{
get
{
return Published == Updated && !string.IsNullOrEmpty(Published);
}
}
}
public class Author
{
public string Name { get; set; }
public string Uri { get; set; }
}
When receiving subscription confirmation, it is necessary to return the hub.challange
information as Response
body. The parameter is sent in the query string only when subscribing, this can be used to distinguish between subscription and notification.
Atom feed needs to be converted into SyndicationFeed
, this requires a reference to System.ServiceModel.Syndication
. To get the reference, just add System.ServiceModel
as service reference.
[HttpGet]
[HttpPost]
public async Task<HttpResponseMessage> NotificationCallback()
{
try
{
var re = HttpContext.Current.Request;
string challenge = re.QueryString["hub.challenge"];
if (string.IsNullOrEmpty(challenge))
{
var stream = re.InputStream;
var data = ConvertAtomToSyndication(stream);
challenge = "OK";
}
else
{
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(challenge),
ReasonPhrase = challenge,
StatusCode = HttpStatusCode.OK
}
}
catch (Exception ex)
{
}
}
private YoutubeNotification ConvertAtomToSyndication(Stream stream)
{
using(var xmlReader = XmlReader.Create(stream))
{
SyndicationFeed feed = SyndicationFeed.Load(xmlReader);
var item = feed.Items.FirstOrDefault();
return new YoutubeNotification()
{
ChannelId = GetElementExtensionValueByOuterName(f, "channelId"),
VideoId = GetElementExtensionValueByOuterName(f, "videoId"),
Title = f.Title.Text,
Published = f.PublishDate.ToString("dd/MM/yyyy"),
Updated = f.LastUpdatedTime.ToString("dd/MM/yyyy")
};
}
}
private string GetElementExtensionValueByOuterName(SyndicationItem item, string outerName)
{
if (item.ElementExtensions.All(x => x.OuterName != outerName)) return null;
return item.ElementExtensions.Single(x => x.OuterName == outerName).GetObject<XElement>().Value;
}
Points of Interest
I hope this helps you to understand the working of PubSubBubHub
(Push Notification).
History