Author - Web Developer - Educator
Found 2 results for tag "api"
RSS Feed

Update Youtube Video Privacy with API and OAuth

OAuthI posted this on my Facebook and Github, but I thought I'd post it here for a more public audience.

Recently, while working on Japanoblog, I realized that there was a problem: when we created videos, we would post them for our Patreon supporters 3 days early so they could get early access to them. However, I had a few options:
  1. I could post a separate video to Youtube/Vimeo/etc and share the link, but then wait 3 days and upload the real video and take down the temp one (which would mean 2x the work, and misleading stats)
  2. I could make the video "Unlisted" and share the link with our Patreon supporters, then wait 3 days and change the video to "Public" for all, or
  3. Do the 2nd option, but then have the server automatically change the status to "Public" when the video was ready to go public (as per the blog post publishing date

Well, being the pragmatic programmer that I am, I figured out how to make the server do it for me. But it was not an easy task (apparently, nobody had done this before - at least, on a public searchable point). After scouring the internet to dissect the Youtube API, Google OAuth (along with token creation), researching and pulling parts from about 7 different public user projects, and some patient testing, I finally have it...I hope.

This script is based on Dom Sammut's code and the Youtube Sample Code (PHP #1).

(Don't want to copy/paste? Here's the Github repository)

So, without further ado, here is what I have come up with:

First: get your tokens


You need to generate your tokens to get the process started.
<?php
 

 
#Primary code from https://www.domsammut.com/code/php-server-side-youtube-v3-oauth-api-video-upload-guide/
 
#Create Client ID and Client Secret by creating OAuth credentials 
 
# at https://console.developers.google.com/apis/credentials
 
# MAKE SURE YOU UPDATE YOUR REDIRECT URL TO MATCH!!!!!!!!!
 
$CLIENT_ID = "XXXXXXXXXXXXXX.apps.googleusercontent.com";
 
$CLIENT_SECRET = "XXXXXXXXXXX";
 
$application_name="APPLICATION_NAME";
 
 
 
// Call set_include_path() as needed to point to your client library.
 
#set_include_path($_SERVER['DOCUMENT_ROOT'] . '/directory/to/google/api/');
 
#Download the PHP Client Library from Google at https://developers.google.com/api-client-library/php/
 

 
#This has been installed using Composer - update if you download the files directly
 
set_include_path(get_include_path() . PATH_SEPARATOR . '/PATH/TO/vendor/google/apiclient/src/');
 
require_once 'Google/Client.php';
 
require_once 'Google/Service/YouTube.php';
 
session_start();
 
 
 
/*
 
 * You can acquire an OAuth 2.0 client ID and client secret from the
 
 * {{ Google Cloud Console }} <{{ https://cloud.google.com/console }}>
 
 * For more information about using OAuth 2.0 to access Google APIs, please see:
 
 * <https://developers.google.com/youtube/v3/guides/authentication>
 
 * Please ensure that you have enabled the YouTube Data API for your project.
 
 */
 
$OAUTH2_CLIENT_ID = $CLIENT_ID;
 
$OAUTH2_CLIENT_SECRET = $CLIENT_SECRET;
 
#$REDIRECT = 'http://localhost/oauth2callback.php';
 
$REDIRECT = 'http://YOUR_URL.com/oauth2callback.php';
 
$APPNAME = $application_name;
 
 
 
 
 
$client = new Google_Client();
 
$client->setClientId($OAUTH2_CLIENT_ID);
 
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
 
$client->setScopes('https://www.googleapis.com/auth/youtube');
 
$client->setRedirectUri($REDIRECT);
 
$client->setApplicationName($APPNAME);
 
$client->setAccessType('offline');
 
 
 
 
 
// Define an object that will be used to make all API requests.
 
$youtube = new Google_Service_YouTube($client);
 
 
 
if (isset($_GET['code'])) {
 
    if (strval($_SESSION['state']) !== strval($_GET['state'])) {
 
        die('The session state did not match.');
 
    }
 
 
 
    $client->authenticate($_GET['code']);
 
    $_SESSION['token'] = $client->getAccessToken();
 
 
 
}
 
 
 
if (isset($_SESSION['token'])) {
 
    $client->setAccessToken($_SESSION['token']);
 
    echo '<code>' . $_SESSION['token'] . '</code>';
 
}
 
 
 
// Check to ensure that the access token was successfully acquired.
 
if ($client->getAccessToken()) {
 
    try {
 
        // Call the channels.list method to retrieve information about the
 
        // currently authenticated user's channel.
 
        $channelsResponse = $youtube->channels->listChannels('contentDetails', array(
 
            'mine' => 'true',
 
        ));
 
 
 
        $htmlBody = '';
 
        foreach ($channelsResponse['items'] as $channel) {
 
            // Extract the unique playlist ID that identifies the list of videos
 
            // uploaded to the channel, and then call the playlistItems.list method
 
            // to retrieve that list.
 
            $uploadsListId = $channel['contentDetails']['relatedPlaylists']['uploads'];
 
 
 
            $playlistItemsResponse = $youtube->playlistItems->listPlaylistItems('snippet', array(
 
                'playlistId' => $uploadsListId,
 
                'maxResults' => 50
 
            ));
 
 
 
            $htmlBody .= "<h3>Videos in list $uploadsListId</h3><ul>";
 
            foreach ($playlistItemsResponse['items'] as $playlistItem) {
 
                $htmlBody .= sprintf('<li>%s (%s)</li>', $playlistItem['snippet']['title'],
 
                    $playlistItem['snippet']['resourceId']['videoId']);
 
            }
 
            $htmlBody .= '</ul>';
 
        }
 
    } catch (Google_ServiceException $e) {
 
        $htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>',
 
            htmlspecialchars($e->getMessage()));
 
    } catch (Google_Exception $e) {
 
        $htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>',
 
            htmlspecialchars($e->getMessage()));
 
    }
 
 
 
    $_SESSION['token'] = $client->getAccessToken();
 
} else {
 
    $state = mt_rand();
 
    $client->setState($state);
 
    $_SESSION['state'] = $state;
 
 
 
    $authUrl = $client->createAuthUrl();
 
    $htmlBody = <<<END
 
  <h3>Authorization Required</h3>
 
  <p>You need to <a href="$authUrl">authorise access</a> before proceeding.<p>
 
END;
 
}
 
?>
 
 
 
<!doctype html>
 
<html>
 
<head>
 
    <title>My Uploads</title>
 
</head>
 
<body>
 
<?php echo $htmlBody?>
 
</body>
 
</html>


Now that that's all set, save the response to a file (I recommend "the_key.txt"), and modify and run the following:
<?php
 
/**
 
 * This code is to be run automatically to update a Youtube video's privacy status
 
 *
 
 * First, generate your key using "get-token.php" - read the notes below for generation
 
 * Next, update this file with the appropriate information (path to key file, Client ID, 
 
 *    Client Secret (OAuth Required), Application Name, Database Login, Database Query, and
 
 *    location of PHP Client Library - all download information is below)
 
 * 
 
 * @author Kyle Perkins
 
 * @site https://github.com/kode29/google-youtube-api-privacystatus
 
 * 
 
 * NOTICE: Rest of copyright should be in tact for other scripts (Dom Sammut (domsammut.com) and Ibrahim Ulukaya (Google)
 
 * Last Update: 20160108
 
**/
 

 
#Primary code from https://www.domsammut.com/code/php-server-side-youtube-v3-oauth-api-video-upload-guide/
 
# Mixed with sample code from https://developers.google.com/youtube/v3/docs/videos/update (PHP #1)
 

 

 
#Generate the "the_key" with get-token.php and store it into "the_key.txt" or wherever you want to store it BEFORE running this script.
 
# Also, make sure "the_key" has a REFRESH TOKEN!
 
$key_file = "/path/to/the_key.txt";
 

 
#Create Client ID and Client Secret by creating OAuth credentials 
 
# at https://console.developers.google.com/apis/credentials
 
# MAKE SURE YOU UPDATE YOUR REDIRECT URL TO MATCH!!!!!!!!!
 
$CLIENT_ID = "XXXXXXXXXXXXXX.apps.googleusercontent.com";
 
$CLIENT_SECRET = "XXXXXXXXXXX";
 
$application_name="APPLICATION-NAME";
 

 
#CHeck the DB for updated videos
 
$video_list=array();
 
    $dbh = new PDO('mysql:host=localhost;dbname=DATABASE_NAME', "DATABASE_USER", "DATABASE_PW");
 

 
	$sql="select `video` from `TABLE` where `stamp` like '".date("Y-m-d H:i:")."%'";
 
				$query = $dbh -> prepare($sql);
 
				$query->execute();
 
				if ($query->rowCount() > 0){ #rowCount() won't work on some databases
 
					$values = $query->fetch(PDO::FETCH_ASSOC);
 
					while (list($key, $value) = each($values)){
 
						$video_list[]=$value;
 
					}
 
				}
 
$key = file_get_contents($key_file);
 
if (count($video_list)>0){
 
foreach($video_list as $VIDEO_ID){
 
	$VIDEO_ID = str_replace("https://youtube.com/watch?v=", "", $VIDEO_ID);
 
	$VIDEO_ID = str_replace("https://youtu.be/", "", $VIDEO_ID);
 

 
#Sample $VIDEO_ID can be "gYY3fVz6PjY";
 
/**
 
 * This sample adds new tags to a YouTube video by:
 
 *
 
 * 1. Retrieving the video resource by calling the "youtube.videos.list" method
 
 *    and setting the "id" parameter
 
 * 2. Appending new tags to the video resource's snippet.tags[] list
 
 * 3. Updating the video resource by calling the youtube.videos.update method.
 
 *
 
 * @author Ibrahim Ulukaya
 
*/
 

 
// Call set_include_path() as needed to point to your client library.
 
#Download the PHP Client Library from Google at https://developers.google.com/api-client-library/php/
 

 
#This has been installed using Composer - update if you download the files directly
 
set_include_path(get_include_path() . PATH_SEPARATOR . '/PATH/TO/vendor/google/apiclient/src/');
 
    
 
require_once 'Google/Client.php';
 
require_once 'Google/Service/YouTube.php';
 
session_start();
 

 
/*
 
 * You can acquire an OAuth 2.0 client ID and client secret from the
 
 * Google Developers Console <https://console.developers.google.com/>
 
 * For more information about using OAuth 2.0 to access Google APIs, please see:
 
 * <https://developers.google.com/youtube/v3/guides/authentication>
 
 * Please ensure that you have enabled the YouTube Data API for your project.
 
 */
 
$OAUTH2_CLIENT_ID = $CLIENT_ID;
 
$OAUTH2_CLIENT_SECRET = $CLIENT_SECRET;
 

 
$client = new Google_Client();
 
$client->setClientId($OAUTH2_CLIENT_ID);
 
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
 
$client->setScopes('https://www.googleapis.com/auth/youtube');
 

 
#$redirect = filter_var('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'], FILTER_SANITIZE_URL);
 
# If running via Cron, HTTP_HOST may be blank
 
$redirect = filter_var('http://YOUR_URL/' . $_SERVER['PHP_SELF'], FILTER_SANITIZE_URL);
 
$client->setRedirectUri($redirect);
 

 
$scope=array("https://www.googleapis.com/auth/youtube", "https://www.googleapis.com/auth/youtubepartner", "https://www.googleapis.com/auth/youtube.forcessl");
 

 
// Define an object that will be used to make all API requests.
 

 

 
#if (isset($_GET['code'])) {
 
#  if (strval($_SESSION['state']) !== strval($_GET['state'])) {
 
#    die('The session state did not match.');
 
#  }
 
#
 
#  $client->authenticate($_GET['code']);
 
#  $_SESSION['token'] = $client->getAccessToken();
 
#  header('Location: ' . $redirect);
 
#}
 
#
 
#if (isset($_SESSION['token'])) {
 
#  $client->setAccessToken($_SESSION['token']);
 
#}
 
$client_id = $CLIENT_ID;
 
$client_secret = $CLIENT_SECRET;
 
#var_dump($key);
 

 
  $client = new Google_Client();
 
    $client->setApplicationName($application_name);
 
    $client->setClientId($client_id);
 
    $client->setAccessType('offline');
 
    $client->setAccessToken($key);
 
    $client->setScopes($scope);
 
    $client->setClientSecret($client_secret);
 

 
// Check to ensure that the access token was successfully acquired.
 
if ($client->getAccessToken()) {
 
/**
 
         * Check to see if our access token has expired. If so, get a new one and save it to file for future use.
 
         */
 
        if($client->isAccessTokenExpired()) {
 
            $newToken = json_decode($client->getAccessToken());
 
            $client->refreshToken($newToken->refresh_token);
 
		#This is for debugging if your token is not regenerated
 
	    #var_dump($client->getAccessToken());
 
            file_put_contents($key_file, $client->getAccessToken());
 
        }
 

 
$youtube = new Google_Service_YouTube($client);
 

 
  try{
 

 
    // REPLACE this value with the video ID of the video being updated.
 
    $videoId = $VIDEO_ID;
 

 
    // Call the API's videos.list method to retrieve the video resource.
 
    $listResponse = $youtube->videos->listVideos("status", array('id'=>$videoId));
 

 
#	array( 'id' => $VIDEO_ID, 'status' => array('privacyStatus' => 'public')));
 

 
    // If $listResponse is empty, the specified video was not found.
 
    if (empty($listResponse)) {
 
      $htmlBody .= sprintf('<h3>Can't find a video with video id: %s</h3>', $videoId);
 
    } else {
 
      // Since the request specified a video ID, the response only
 
      // contains one video resource.
 
      $video = $listResponse[0];
 
	$videoStatus = $video['status'];
 
	$videoStatus->privacyStatus = 'public'; #privacyStatus options are public, private, and unlisted
 
	$video->setStatus($videoStatus);
 
	$updateResponse = $youtube->videos->update('status', $video);
 

 

 
#    $htmlBody .= "<h3>Video Updated</h3><ul>";
 
#    $htmlBody .= sprintf('<li>Tags "%s" and "%s" added for video %s (%s) </li>',
 
#        array_pop($responseTags), array_pop($responseTags),
 
#        $videoId, $video['snippet']['title']);
 
#    $htmlBody .= '</ul>';
 
$htmlBody = "We're Good!"; #Just a debug phrase to know that the script completed successfully. Not required to output
 

 
  }
 
    } catch (Google_Service_Exception $e) {
 
      $htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>',
 
          htmlspecialchars($e->getMessage()));
 
    } catch (Google_Exception $e) {
 
      $htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>',
 
          htmlspecialchars($e->getMessage()));
 
    }
 

 
    $_SESSION['token'] = $client->getAccessToken();
 
    } else {
 
      // If the user hasn't authorized the app, initiate the OAuth flow
 
      $state = mt_rand();
 
      $client->setState($state);
 
      $_SESSION['state'] = $state;
 

 
      $authUrl = $client->createAuthUrl();
 
      $htmlBody = <<<END
 
  <h3>Authorization Required</h3>
 
  <p>You need to <a href="$authUrl">authorize access</a> before proceeding.<p>
 
END;
 
    }
 
#      echo "<body>$htmlBody</body>";
 
}}
 
	?>
 

Again, Here's the Github repository)


Tags:#php #mysql #japanoblog #video #youtube #api #oauth

RSS Feed

HTML API: Intro to Web Notifications API

After taking some time to get back into my craft, I realized that I had missed a lot of advancements in the HTML world, especially the evolution of the HTML API. Yes, there actually is an HTML API.

While researching some of the "new" API, I came across a bunch of articles by Aurelio De Rosa via SitePoint (a lot of good stuff there), and one of the items that caught my attention was the "Web Notifications API." Now, I will say that this stuff is still experimental (see the W3C's Working Draft for Notifications, so it's not widely supported and may even disappear. But for now, it's pretty awesome!)

Anyway, I'm sure you're not here to hear read about my day, so let's get to the point of the matter: HTML API - specifically, the Web Notifications API.

I know a lot of you just want to get to the demo and play with it, so here's a Working Demo (Chrome, Firefox, and Safari 7+ only)


What are "Web Notifications"?


If you use an email system like Gmail or Outlook, chances are you've already seen something like this:
Web Notification in the wild!

Well, those are technically desktop notifications and are usually controlled by software, not browsers. (Prior to HTML5, in order to get Desktop Notifications for Gmail, you had to download the "Gmail Notifier" application, and Outlook is already a software program to begin with).

The main difference is that these notifications (like the one above) are controlled by the browser - yes, the browser can control (certain) desktop notifications, and it's actually quite simple.

How to Use It


In an HTML file, add this bit of Javascript:
var notification = new Notification("New Email Received", { icon: "mail.png" })
That's pretty much it. No external libraries to load, no frameworks to bend around your whim - it's automatically built in to most modern browsers. Now, I know what you're asking: "What? That seems too simple! There has to be more." Don't worry, that was just the extremely core concept (provided by W3C). Technically, there is one more step to do: your application must first ask for permission before doing anything with the visitor's computer. (Yes, I know this seems a bit backwards, but for privacy, I can understand - especially with all of the ActiveX IE vaulnerabilities out there). This seems that the W3C wants to get rid of, but for now, it stays. (W3C says: Warning: In designing the platform notifications are the one instance thus far where asking the user upfront makes sense. Specifications for other APIs should not use this pattern and instead employ one of the many more suitable alternatives. [ref])
Notification.requestPermission(function(){
 
    //we're declaring a new object here
 
    var notification = new Notification(title, options);
 
});
There are 3 states to the "Permission" request: default (equivilant to "denied", because a choice has not been made yet), denied (the user denied the access, and therefore the request cannot proceed), or granted (the green light). This is similiar to when a browser (and/or application) asks to automatically use your physical location to determine items near you, like so:

Geolocation API in use

If you haven't already allowed a site to display desktop notifications (or allowed for geolocation, as the above image), then it will ask you for permission before proceeding:

Asking Permission

Overall, in accordance with "best practices", here's what I have learned/uncovered/researched:

First, you need to check to see if the API is supported. As this is still in the works, it isn't fully supported everywhere (for example, only Chrome, Firefox, and Safari 7+ support this - see http://caniuse.com/#feat=notifications for the full supported range).

In order to detect if the API is supported, add this bit of JS before you go calling Notifications.
//check to see if the API is supported
 
if (!('Notification' in window)){
 
    //API not supported. Show a message or something
 
    ....
 
} else {
 
    // API is supported. Do things
 
    ....
 
}
Therefore, if you have any buttons, links, or other items that make the notifications appear based on calling them, then you can disable them safely (or, if you are basing it off of automatic notifications, like emails, scores, or calendars, you can safety defer the notifications instead of having an error thrown).

Let's get fancy


The above sample is pretty much all you need to know for calling Notifications. If you want to get a little fancy with things, try this:
title = 'Email Received';
 
options = {
 
    body: 'You have a total of 3 unread emails',
 
    tag: 'preset', //The UID of the notification
 
    icon: 'https://mail.google.com/mail/u/0/images/favicon2.ico'
 
};
Before we go into detail about this, let's break it down:

  • title: This is the title of the Notification (duh)

  • options: This outlines a few of the Instance options available in the API
    (technically, everything is optional)

    • dir: This determines the text direction that the notification will be displayed (options are auto, ltr (left to right), or rtl (right to left) (default: auto)

    • lang: The language that the notification should be displayed in (example: 'en' for 'english') - (default: default language for computer)

    • body: The message that is displayed in the notification, like the body of a letter (default: blank)

    • tag: I like to think of this as the UID (Unique IDentifier) of the notification. Examples in the W3C Draft Documentation show usage of multiple instances of notification windows, so if you want one for your Email, Calendar, Reminders, etc, you can name each appropriately instead of just having to fall back on just 1. (default: blank)

    • icon: If you have a jpg, png, ico, or any other image file to show with the notification, you can list it here (I'd recommend the full absolute path of a web file instead of locally) (default: blank)

Those are all of the options available for the Notification API right now. Maybe more will come in the future - it just depends on what the official word is on the development.

FAQ


Can the notification automatically close after a few seconds?

I'd like to say 'yes', but the answer is unfortunately, no - at least, not completely.

I've tested my demo in Chrome and Firefox and had different results. I set up a setTimeout right after the notification is shown and tested it.
notification.onshow = function() { setTimeout(notification.close, 15000) }
However, Firefox displays the notification and automatically dismisses it after 5 seconds, regardless of the timeout, and Chrome will display the notification until you close it. Apparently, the problem is that the drafted Event Handlers don't register in the browsers, yet. I've tried to implement a calling feature of event.type == 'onshow', then call the setTimeout function, but to no avail. I've also tried to match up the event.type prior to the forEach function, but yet again, nothing.

Demo

In case you missed it above, here's the live demo again. It provides a call button that calls the notification on demand. I'm sure you can play around with it and figure out how it works.

References

Mozilla Dev Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Notification W3C Draft: w3.org/TR/notifications/ Articles by Aurelio De Rosa: sitepoint.com/author/aderosa/ Aurelio's Notifications API Demo: http://aurelio.audero.it/demo/web-notifications-api-demo.html "Can I Use" browser feature comparison: http://caniuse.com/#feat=notifications


Tags:#html #api #javascript #w3c #mozilla #demos #tutorials