Our goal is simple: to be able to access YouTube subscriptions, channels and uploads from code, using official APIs (no scraping).

This tutorial will for most part follow existing Google sample but expand on some of the points I personally struggled with. Google is offering APIs in several languages, but I find using JavaScript as the most cut-to-the-chase approach.

So, lets get started.

Step 1: Setting-up the project

Good news: Our entire project is going to consist of just a single index.html file.
Bad news: You still need a server to host it, since Google APIs will refuse to work if you just open the html file in the browser.

For prototyping phase we will need a web-server. Any old web-server will do. I recommend getting node.js and then running npm install http-server -g to install http-server. You can start the server by running http-server.
Create new file named index.html in the directory you used to install and run http-server. Now, whatever you put into index.html will be available at http://localhost:8080.

The thing that you want to put into index.html is pretty-much the final code snippet from Google’s guide:

<script>
    var GoogleAuth;
    var SCOPE = 'https://www.googleapis.com/auth/youtube.force-ssl';
    function handleClientLoad() {
        // Load the API's client and auth2 modules.
        // Call the initClient function after the modules load.
        gapi.load('client:auth2', initClient);
    }

    function initClient() {
        // Retrieve the discovery document for version 3 of YouTube Data API.
        // In practice, your app can retrieve one or more discovery documents.
        var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest';

        // Initialize the gapi.client object, which app uses to make API requests.
        // Get API key and client ID from API Console.
        // 'scope' field specifies space-delimited list of access scopes.
        gapi.client.init({
            'apiKey': 'YOUR_API_KEY',
            'discoveryDocs': [discoveryUrl],
            'clientId': 'YOUR_CLIENT_ID',
            'scope': SCOPE
        }).then(function () {
            GoogleAuth = gapi.auth2.getAuthInstance();

            // Listen for sign-in state changes.
            GoogleAuth.isSignedIn.listen(updateSigninStatus);

            // Handle initial sign-in state. (Determine if user is already signed in.)
            var user = GoogleAuth.currentUser.get();
            setSigninStatus();

            // Call handleAuthClick function when user clicks on
            //      "Sign In" button.
            $('#sign-in-or-out-button').click(function() {
                handleAuthClick();
            }); 
        });
    }

    function handleAuthClick() {
        if (GoogleAuth.isSignedIn.get()) {
            // User is authorized and has clicked 'Sign out' button.
            GoogleAuth.signOut();
        } else {
            // User is not signed in. Start Google auth flow.
            GoogleAuth.signIn();
        }
    }

    function setSigninStatus(isSignedIn) {
        var user = GoogleAuth.currentUser.get();
        var isAuthorized = user.hasGrantedScopes(SCOPE);
        if (isAuthorized) {
            $('#sign-in-or-out-button').html('Sign out');
        } else {
            $('#sign-in-or-out-button').html('Sign In');
        }
    }

    function updateSigninStatus(isSignedIn) {
        setSigninStatus();
    }
</script>

<button id="sign-in-or-out-button">Sign In</button>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js" 
    onload="this.onload=function(){};handleClientLoad()" 
    onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>

After you save index.html and refresh http://localhost:8080, you should expect to see a Sign-In button, but it will not yet work because of the missing YOUR_CLIENT_ID and YOUR_API_KEY.

Step 2: Getting the API keys

To both develop and test our newly created page, you will need a valid Google account. Start by going to console.developers.google.com, sign-in and agree to the terms and conditions.

There go to credentials. You will be asked to create a project (unless you done this already):

credentials

Give a name to your project (it is not important as far as I can tell) and proceed to the Credentials page. There you will have the option to create various types of credentials:

credentials

Lets begin with creating an API key. It’s pretty much a one-click process, at the end of which you will get a long string you need to paste into your code instead of YOUR_API_KEY.

Next, lets create OAuth client ID:

credentials

You will be asked to configure consent screen:

credentials

The only thing you need to input is your App name, that can be anything you want. On the next page, select Web Application:

credentials

Note the Authorized JavaScript origins field. Enter http://localhost:8080. Google will only allow API requests coming from this host. We will revisit this point when we will discuss hosting.

Copy the resulting Client ID (not the Client secret) and paste it over YOUR_CLIENT_ID in index.html.

Before we move on to code, there is one more thing you need to do in the Developers Console – go to the API Library and search for YouTube:

credentials

Click on the YouTube Data API v3, then click ENABLE.

One problem I have encountered at this point is that the page will still fail to load, complaining in the developer console about Authorized Origins. If you are using Chrome, go to More Tools > Clear browsing data.... Under Time Range you can select Last hour and click CLEAR DATA. Now, when you refresh http://localhost:8080 the sign-in button should work:

credentials

Step 3: Sending Requests

Next, we want to fetch and iterate over the list of user’s subscriptions. Add the following function to your <script> block:

function get_subscriptions() {
    var rq = {
        part: 'id,contentDetails,subscriberSnippet,snippet',
        mine: true,
        maxResults: 50
    };
    
    var request = gapi.client.youtube.subscriptions.list(rq);
    
    request.execute(function(response) {
        console.log(response.items);
    });
}

Don’t forget to call get_subscriptions(); somewhere, good place would be the setSigninStatus function.

Calling YouTube API generally involves these steps:

  1. Define request parameters
  2. Generate a request using one of the API functions, in this case list
  3. Execute the request with a callback of what to do on completion

In this example, you should see the list of up to 50 subscription objects in your Chrome Developer Console (F12). Note the number 50. In the request, we specify maxResults to be 50, but what if we don’t want to get just the first 50 results? Can’t we just pass 100,000 or infinity? Well, you could, but you’d still get at most 50 results. Instead, with every response Google will provide you a token you can use to get to the next page of results:

function get_subscriptions(token) {
    var rq = {
        part: 'id,contentDetails,subscriberSnippet,snippet',
        mine: true,
        maxResults: 50
    };
    if (token) { // If we got a token from previous call
        rq.pageToken = token; // .. attach it to the new request
    }
    var request = gapi.client.youtube.subscriptions.list(rq);
    
    request.execute(function(response) {
        console.log(response.items);
        
        var next = response.nextPageToken; // get token for next page
        if (next) { // if has next
            get_subscriptions(next); // recurse with the new token
        }
    });
}

This updated snippet will print all the subscriptions in bundles of 50. You can hack this function to output only limited number of subscriptions or a random subscription if you’d like.

Lets now add some information about each subscription using another API request. Instead of console.log(response.items);, we will iterate over all subscriptions and fetch the channel ID:

for (var i = 0; i < response.items.length; i++) {
    var itm = response.items[i];
    var cid = itm.snippet.resourceId.channelId;
    get_channel(cid);
}

To pretty-print each channel, we will introduce another function:

function get_channel(cid) {
    var request = gapi.client.youtube.channels.list({
        part: 'snippet,contentDetails,statistics',
        id: cid
    });
    request.execute(function(response) {
        var channels = response.items;
        console.log(channels[0].snippet.title);
    });
}

Step 4: Getting Channel Uploads

We will expand on our newly acquired knowledge of API requests by adding the latest upload from every channel. To make things more fun, lets add a named div to our HTML:

.. </script>

<button id="sign-in-or-out-button">Sign In</button>
<div id="results"></div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> ..

The way to get to channels uploads on YouTube is using the playlist API. Lets replace console.log(channels[0].snippet.title); in get_channel with

get_upload(channels[0].contentDetails.relatedPlaylists.uploads);

We will define get_upload like this:

function get_uploads(pid) {
    var rq = {
        part: 'snippet,contentDetails',
        playlistId: pid,
        maxResults: 50
    };
    
    var request = gapi.client.youtube.playlistItems.list(rq);
    
    request.execute(function(response) {
        var itm = response.items[0];
        // Prepare clickable HTML snippet:			
        var thumb = itm.snippet.thumbnails.medium;
        var link = "<a href='https://www.youtube.com/watch?v=" 
            + itm.snippet.resourceId.videoId + "' target='_blank'>";
        var img = "<img src='" + thumb.url + "' width=" + thumb.width 
            + " height=" + thumb.height + ">";
        var end_link = "</a>";
        // Add it to results div content:
        $('#results').append(link + img + end_link);
    });
}

Refreshing the page will give you a list of clickable thumbnails from your YouTube subscriptions:

credentials

Download the code so-far (right-click, save-as)

Next Steps

With under 150 lines of code we managed to create something fun and potentially useful.

Its up to your creativity to take it from here, but I can give you some pointers:

Hosting

There are plenty of solutions to host your page. Here is one way to go about it:

  • Register a user on GitHub and create a repository
  • Clone your repository using git clone
  • Copy index.html to the newly cloned folder and make a commit (git add . and git commit -m "first commit")
  • Push to your remote repository using git push
  • Go to Settings page of your repository on GitHub
  • Enable GitHub Pages and select master branch as source

credentials

Your site is now hosted on GitHub under https://your-user-name.github.io/your-repo-name.

Don’t forget to go back to console.developers.google.com and add https://your-user-name.github.io to Authorized JavaScript origins of your OAuth 2.0 client ID.

Error Handling

At the moment the code is not very robust to failures due to failed authentication, empty list of subscriptions, channels without uploads, etc… It would require small extra effort to handle all these corner cases.

Quotas

Please keep in mind that Google does not offer you unlimited calls to its API and will cut you off for the day if you start spamming requests. You can monitor your quota usage at console.developers.google.com. As long as you don’t do anything stupid, these limitations should be more than enough.

Decorating

This is the fun part 🙂 With core functionality in place, you can start adding UI frameworks like bootstrap, fonts from Google Fonts and glyphs from FontAwesome. The possibilities are endless, enjoy!

This tutorial is based on youtube-shuffle GitHub project.
You can see the final result here