diff --git a/apps-script/quickstart.gs b/apps-script/quickstart.gs new file mode 100644 index 00000000..78f51cdc --- /dev/null +++ b/apps-script/quickstart.gs @@ -0,0 +1,36 @@ +// Note: Apps Script automatically requests authorization +// based on the API's used in the code. + +function channelsListByUsername(part, params) { + var response = YouTube.Channels.list(part, + params); + var channel = response.items[0]; + var dataRow = [channel.id, channel.snippet.title, channel.statistics.viewCount]; + SpreadsheetApp.getActiveSpreadsheet().appendRow(dataRow); +} + +function getChannel() { + var ui = SpreadsheetApp.getUi(); + var channelName = ui.prompt("Enter the channel name: ").getResponseText(); + channelsListByUsername('snippet,contentDetails,statistics', + {'forUsername': channelName}); +} + +function getGoogleDevelopersChannel() { + channelsListByUsername('snippet,contentDetails,statistics', + {'forUsername': 'GoogleDevelopers'}); +} + +function onOpen() { + var firstCell = SpreadsheetApp.getActiveSheet().getRange(1, 1).getValue(); + if (firstCell != 'ID') { + var headerRow = ["ID", "Title", "View count"]; + SpreadsheetApp.getActiveSpreadsheet().appendRow(headerRow); + } + var ui = SpreadsheetApp.getUi(); + ui.createMenu('YouTube Data') + .addItem('Add channel data', 'getChannel') + .addSeparator() + .addItem('Add GoogleDevelopers data', 'getGoogleDevelopersChannel') + .addToUi(); +} diff --git a/apps-script/snippets/README.md b/apps-script/snippets/README.md new file mode 100644 index 00000000..7c7f00c5 --- /dev/null +++ b/apps-script/snippets/README.md @@ -0,0 +1,32 @@ +# Apps Script Code Snippets + +The `youtube-data-api.gs` file in this directory contains code snippets that are generated +by the Data API code snippet tool at: +https://developers.google.com/youtube/v3/code_samples/code_snippets + +You can use that tool to test different parameter values and to generate code samples with +those modified parameter values. The tool generates code for several other programming +languages as well. + +Each function in the file demonstrates a particular use case for a particular API method. +For example, there are several different use cases for calling the `search.list()` method, +such as searching by keyword or searching for live events. + +In addition to the use-case-specific functions, the file also contains some boilerplate code +that prints some data from an API response to the logging console. The print function is +currently designed just to show that each API response returns data and serves as a placeholder +for any function that would actually process an API response. + +## Running these samples + +To run these samples: + +1. Create a spreadsheet in [Google Drive](https://spreadsheets.google.com). +2. Select **Tools > Script Editor** from the menu bar. +3. Paste this code into the script editor and save your file. +4. In the script, select **Resources > Advanced Google Services** and toggle the option for the + YouTube Data API to on. +5. Click the link to the Google Developers Console and enable the YouTube Data API for the project. +6. Go back to the script editor and click 'OK' to indicate that you have finished enabling advanced services. +7. Run the `Main` function in your script. +8. Select **View > Logs** to see the output from the script. diff --git a/apps-script/snippets/apps-script.gs b/apps-script/snippets/apps-script.gs new file mode 100644 index 00000000..1ac8e92d --- /dev/null +++ b/apps-script/snippets/apps-script.gs @@ -0,0 +1,475 @@ +// Sample Apps Script code for printing API response data +function printResults(response) { + var props = ['type', 'title', 'textDisplay', 'channelId', 'videoId', 'hl', 'gl', 'label']; + for (var r = 0; r < response['items'].length; r++) { + var item = response['items'][r]; + var itemId = ''; + var value; + if (item['rating']) { + itemId = item['id']; + value = 'Rating: ' + item['rating']; + } else { + if (item['id']['videoId']) { + itemId = item['id']['videoId']; + } else if (item['id']['channelId']) { + itemId = item['id']['channelId']; + } else { + itemId = item['id']; + } + + for (var p = 0; p < props.length; p++) { + if (item['snippet'][props[p]]) { + value = itemId + ': ' + item['snippet'][props[p]]; + break; + } + } + } + Logger.log(value); + } +} + +/** + * This example retrieves the 25 most recent activities for the Google Developers + * channel. It retrieves the snippet and contentDetails parts for each activity + * resource. + */ +function activitiesList(part, channelId, maxResults) { + var response = YouTube.Activities.list(part, + {'channelId': channelId, + 'maxResults': maxResults}); + printResults(response); +} + +/** + * This example retrieves the 25 most recent activities performed by the user + * authorizing the API request. + */ +function activitiesListMine(part, maxResults, mine) { + var response = YouTube.Activities.list(part, + {'maxResults': maxResults, + 'mine': mine}); + printResults(response); +} + +/** + * This example lists caption tracks available for the Volvo Trucks "Epic Split" + * commercial, featuring Jean-Claude Van Damme. (This video was selected because + * it has many available caption tracks and also because it is awesome.) + */ +function captionsList(part, videoId) { + var response = YouTube.Captions.list(part, videoId); + printResults(response); +} + +/** + * This example retrieves channel data for the GoogleDevelopers YouTube channel. + * It uses the id request parameter to identify the channel by its YouTube channel + * ID. + */ +function channelsListById(part, id) { + var response = YouTube.Channels.list(part, + {'id': id}); + printResults(response); +} + +/** + * This example retrieves channel data for the GoogleDevelopers YouTube channel. + * It uses the forUsername request parameter to identify the channel by its + * YouTube username. + */ +function channelsListByUsername(part, forUsername) { + var response = YouTube.Channels.list(part, + {'forUsername': forUsername}); + printResults(response); +} + +/** + * This example retrieves the channel data for the authorized user's YouTube + * channel. It uses the mine request parameter to indicate that the API should + * only return channels owned by the user authorizing the request. + */ +function channelsListMine(part, mine) { + var response = YouTube.Channels.list(part, + {'mine': mine}); + printResults(response); +} + +/** + * This example retrieves the channel sections shown on the Google Developers + * channel, using the channelId request parameter to identify the channel. + */ +function channelSectionsListById(part, channelId) { + var response = YouTube.ChannelSections.list(part, + {'channelId': channelId}); + printResults(response); +} + +/** + * This example retrieves the channel sections shown on the authorized user's + * channel. It uses the mine request parameter to indicate that the API should + * return channel sections on that channel. + */ +function channelSectionsListMine(part, mine) { + var response = YouTube.ChannelSections.list(part, + {'mine': mine}); + printResults(response); +} + +/** + * This example retrieves comment replies for a specified comment, which is + * identified by the parentId request parameter. In this example, the parent + * comment is the first comment on a video about Apps Script. The video was chosen + * because this particular comment had multiple replies (in multiple languages) + * and also because Apps Script is really useful. + */ +function commentsList(part, parentId) { + var response = YouTube.Comments.list(part, + {'parentId': parentId}); + printResults(response); +} + +/** + * This example retrieves all comment threads associated with a particular + * channel. The response could include comments about the channel or about the + * channel's videos. The request's allThreadsRelatedToChannelId parameter + * identifies the channel. + */ +function commentThreadsListAllThreadsByChannelId(part, allThreadsRelatedToChannelId) { + var response = YouTube.CommentThreads.list(part, + {'allThreadsRelatedToChannelId': allThreadsRelatedToChannelId}); + printResults(response); +} + +/** + * This example retrieves all comment threads about the specified channel. The + * request's channelId parameter identifies the channel. The response does not + * include comments left on videos that the channel uploaded. + */ +function commentThreadsListByChannelId(part, channelId) { + var response = YouTube.CommentThreads.list(part, + {'channelId': channelId}); + printResults(response); +} + +/** + * This example retrieves all comment threads associated with a particular video. + * The request's videoId parameter identifies the video. + */ +function commentThreadsListByVideoId(part, videoId) { + var response = YouTube.CommentThreads.list(part, + {'videoId': videoId}); + printResults(response); +} + +/** + * This example retrieves a list of application languages that the YouTube website + * supports. The example sets the hlparameter value to es_MX, indicating that text + * values in the API response should be provided in that language. That + * parameter's default value is en_US. + */ +function i18nLanguagesList(part, hl) { + var response = YouTube.I18nLanguages.list(part, + {'hl': hl}); + printResults(response); +} + +/** + * This example retrieves a list of content regions that the YouTube website + * supports. The example sets the hlparameter value to es_MX, indicating that text + * values in the API response should be provided in that language. That + * parameter's default value is en_US. + */ +function i18nRegionsList(part, hl) { + var response = YouTube.I18nRegions.list(part, + {'hl': hl}); + printResults(response); +} + +/** + * This example retrieves the list of videos in a specified playlist. The + * request's playlistId parameter identifies the playlist. + +Note that the API + * response does not include metadata about the playlist itself, such as the + * playlist's title and description. Additional metadata about the videos in the + * playlist can also be retrieved using the videos.listmethod. + */ +function playlistItemsListByPlaylistId(part, maxResults, playlistId) { + var response = YouTube.PlaylistItems.list(part, + {'maxResults': maxResults, + 'playlistId': playlistId}); + printResults(response); +} + +/** + * This example retrieves playlists owned by the YouTube channel that the + * request's channelId parameter identifies. + */ +function playlistsListByChannelId(part, channelId, maxResults) { + var response = YouTube.Playlists.list(part, + {'channelId': channelId, + 'maxResults': maxResults}); + printResults(response); +} + +/** + * This example retrieves playlists created in the authorized user's YouTube + * channel. It uses the mine request parameter to indicate that the API should + * only return playlists owned by the user authorizing the request. + */ +function playlistsListMine(part, mine) { + var response = YouTube.Playlists.list(part, + {'mine': mine}); + printResults(response); +} + +/** + * This example retrieves the first 25 search results associated with the keyword + * surfing. Since the request doesn't specify a value for the type request + * parameter, the response can include videos, playlists, and channels. + */ +function searchListByKeyword(part, maxResults, q, type) { + var response = YouTube.Search.list(part, + {'maxResults': maxResults, + 'q': q, + 'type': type}); + printResults(response); +} + +/** + * This example retrieves search results associated with the keyword surfing that + * also specify in their metadata a geographic location within 10 miles of the + * point identified by the location parameter value. (The sample request specifies + * a point on the North Shore of Oahu, Hawaii . The request retrieves the top five + * results, which is the default number returned when the maxResults parameter is + * not specified. + */ +function searchListByLocation(part, location, locationRadius, q, type) { + var response = YouTube.Search.list(part, + {'location': location, + 'locationRadius': locationRadius, + 'q': q, + 'type': type}); + printResults(response); +} + +/** + * This example retrieves a list of acdtive live broadcasts (see the eventType + * parameter value) that are associated with the keyword news. Since the eventType + * parameter is set, the request must also set the type parameter value to video. + */ +function searchListLiveEvents(part, eventType, maxResults, q, type) { + var response = YouTube.Search.list(part, + {'eventType': eventType, + 'maxResults': maxResults, + 'q': q, + 'type': type}); + printResults(response); +} + +/** + * This example searches within the authorized user's videos for videos that match + * the keyword fun. The forMine parameter indicates that the response should only + * search within the authorized user's videos. Also, since this request uses the + * forMine parameter, it must also set the type parameter value to video. + +If you + * have not uploaded any videos associated with that term, you will not see any + * items in the API response list. + */ +function searchListMine(part, maxResults, forMine, q, type) { + var response = YouTube.Search.list(part, + {'maxResults': maxResults, + 'forMine': forMine, + 'q': q, + 'type': type}); + printResults(response); +} + +/** + * This example sets the relatedToVideoId parameter to retrieve a list of videos + * related to that video. Since the relatedToVideoId parameter is set, the request + * must also set the type parameter value to video. + */ +function searchListRelatedVideos(part, relatedToVideoId, type) { + var response = YouTube.Search.list(part, + {'relatedToVideoId': relatedToVideoId, + 'type': type}); + printResults(response); +} + +/** + * This example retrieves a list of channels that the specified channel subscribes + * to. In this example, the API response lists channels to which the GoogleDevelopers channel + * subscribes. + */ +function subscriptionsListByChannelId(part, channelId) { + var response = YouTube.Subscriptions.list(part, + {'channelId': channelId}); + printResults(response); +} + +/** + * This example determines whether the user authorizing the API request subscribes + * to the channel that the forChannelId parameter identifies. To check whether + * another channel (instead of the authorizing user's channel) subscribes to the + * specified channel, remove the mine parameter from this request and add the channelId parameter + * instead. + +In this example, the API response contains one item if you subscribe to + * the GoogleDevelopers channel. Otherwise, the request does not return any items. + */ +function subscriptionsListForChannelId(part, forChannelId, mine) { + var response = YouTube.Subscriptions.list(part, + {'forChannelId': forChannelId, + 'mine': mine}); + printResults(response); +} + +/** + * This example uses the mySubscribers parameter to retrieve the list of channels + * to which the authorized user subscribes. + */ +function subscriptionsListMySubscribers(part, mySubscribers) { + var response = YouTube.Subscriptions.list(part, + {'mySubscribers': mySubscribers}); + printResults(response); +} + +/** + * This example uses the mine parameter to retrieve a list of channels that + * subscribe to the authenticated user's channel. + */ +function subscriptionsListMySubscriptions(part, mine) { + var response = YouTube.Subscriptions.list(part, + {'mine': mine}); + printResults(response); +} + +/** + * This example shows how to retrieve a list of reasons that can be used to report + * abusive videos. You can retrieve the text labels in other languages by + * specifying a value for the hl request parameter. + */ +function videoAbuseReportReasonsList(part) { + var response = YouTube.VideoAbuseReportReasons.list(part); + printResults(response); +} + +/** + * This example retrieves a list of categories that can be associated with YouTube + * videos in the United States. The regionCode parameter specifies the country for + * which categories are being retrieved. + */ +function videoCategoriesList(part, regionCode) { + var response = YouTube.VideoCategories.list(part, + {'regionCode': regionCode}); + printResults(response); +} + +/** + * This example uses the regionCode to retrieve a list of categories that can be + * associated with YouTube videos in Spain. It also uses the hl parameter to + * indicate that text labels in the response should be specified in Spanish. + */ +function videoCategoriesListForRegion(part, hl, regionCode) { + var response = YouTube.VideoCategories.list(part, + {'hl': hl, + 'regionCode': regionCode}); + printResults(response); +} + +/** + * This example retrieves information about a specific video. It uses the id + * parameter to identify the video. + */ +function videosListById(part, id) { + var response = YouTube.Videos.list(part, + {'id': id}); + printResults(response); +} + +/** + * This example retrieves a list of YouTube's most popular videos. The regionCode + * parameter identifies the country for which you are retrieving videos. The + * sample code is set to default to return the most popular videos in the United + * States. You could also use the videoCategoryId parameter to retrieve the most + * popular videos in a particular category. + */ +function videosListMostPopular(part, chart, regionCode, videoCategoryId) { + var response = YouTube.Videos.list(part, + {'chart': chart, + 'regionCode': regionCode, + 'videoCategoryId': videoCategoryId}); + printResults(response); +} + +/** + * This example retrieves information about a group of videos. The id parameter + * value is a comma-separated list of YouTube video IDs. You might issue a request + * like this to retrieve additional information about the items in a playlist or + * the results of a search query. + */ +function videosListMultipleIds(part, id) { + var response = YouTube.Videos.list(part, + {'id': id}); + printResults(response); +} + +/** + * This example retrieves a list of videos liked by the user authorizing the API + * request. By setting the rating parameter value to dislike, you could also use + * this code to retrieve disliked videos. + */ +function videosListMyRatedVideos(part, myRating) { + var response = YouTube.Videos.list(part, + {'myRating': myRating}); + printResults(response); +} + +/** + * This example retrieves the rating that the user authorizing the request gave to + * a particular video. In this example, the video is of Amy Cuddy's TED talk about + * body language. + */ +function videosGetRating(id) { + var response = YouTube.Videos.getRating(id); + printResults(response); +} + +function Main() { + activitiesList('snippet,contentDetails', 'UC_x5XG1OV2P6uZZ5FSM9Ttw', 25); + activitiesListMine('snippet,contentDetails', 25, true); + captionsList('snippet', 'M7FIvfx5J10'); + channelsListById('snippet,contentDetails,statistics', 'UC_x5XG1OV2P6uZZ5FSM9Ttw'); + channelsListByUsername('snippet,contentDetails,statistics', 'GoogleDevelopers'); + channelsListMine('snippet,contentDetails,statistics', true); + channelSectionsListById('snippet,contentDetails', 'UC_x5XG1OV2P6uZZ5FSM9Ttw'); + channelSectionsListMine('snippet,contentDetails', true); + commentsList('snippet', 'z13icrq45mzjfvkpv04ce54gbnjgvroojf0'); + commentThreadsListAllThreadsByChannelId('snippet,replies', 'UC_x5XG1OV2P6uZZ5FSM9Ttw'); + commentThreadsListByChannelId('snippet,replies', 'UCAuUUnT6oDeKwE6v1NGQxug'); + commentThreadsListByVideoId('snippet,replies', 'm4Jtj2lCMAA'); + i18nLanguagesList('snippet', 'es_MX'); + i18nRegionsList('snippet', 'es_MX'); + playlistItemsListByPlaylistId('snippet,contentDetails', 25, 'PLBCF2DAC6FFB574DE'); + playlistsListByChannelId('snippet,contentDetails', 'UC_x5XG1OV2P6uZZ5FSM9Ttw', 25); + playlistsListMine('snippet,contentDetails', true); + searchListByKeyword('snippet', 25, 'surfing', 'video'); + searchListByLocation('snippet', '21.5922529,-158.1147114', '10mi', 'surfing', 'video'); + searchListLiveEvents('snippet', 'live', 25, 'news', 'video'); + searchListMine('snippet', 25, true, 'fun', 'video'); + searchListRelatedVideos('snippet', 'Ks-_Mh1QhMc', 'video'); + subscriptionsListByChannelId('snippet,contentDetails', 'UC_x5XG1OV2P6uZZ5FSM9Ttw'); + subscriptionsListForChannelId('snippet,contentDetails', 'UC_x5XG1OV2P6uZZ5FSM9Ttw', true); + subscriptionsListMySubscribers('snippet,contentDetails,subscriberSnippet', true); + subscriptionsListMySubscriptions('snippet,contentDetails', true); + videoAbuseReportReasonsList('snippet'); + videoCategoriesList('snippet', 'US'); + videoCategoriesListForRegion('snippet', 'es', 'ES'); + videosListById('snippet,contentDetails,statistics', 'Ks-_Mh1QhMc'); + videosListMostPopular('snippet,contentDetails,statistics', 'mostPopular', 'US', ''); + videosListMultipleIds('snippet,contentDetails,statistics', 'Ks-_Mh1QhMc,c0KYU2j0TM4,eIho2S0ZahI'); + videosListMyRatedVideos('snippet,contentDetails,statistics', 'like'); + videosGetRating('Ks-_Mh1QhMc,c0KYU2j0TM4,eIho2S0ZahI'); +} diff --git a/apps-script/youtube.gs b/apps-script/youtube.gs index d4b55344..cf812534 100644 --- a/apps-script/youtube.gs +++ b/apps-script/youtube.gs @@ -1,29 +1,6 @@ -// TITLE: Search by topic -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.search.list -/** - * This function searches for videos that are associated with a particular Freebase - * topic, logging their video IDs and titles to the Apps Script log. This example uses - * the topic ID for Google Apps Script. - * - * Note that this sample limits the results to 25. To return more results, pass - * additional parameters as documented here: - * https://developers.google.com/youtube/v3/docs/search/list - */ -function searchByTopic() { - var mid = '/m/0gjf126'; - var results = YouTube.Search.list('id,snippet', {topicId: mid, maxResults: 25}); - - for(var i in results.items) { - var item = results.items[i]; - Logger.log('[%s] Title: %s', item.id.videoId, item.snippet.title); - } -} - -// TITLE: Search by keyword -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.search.list /** + * Title: Search by keyword + * Method: youtube.search.list * This function searches for videos related to the keyword 'dogs'. The video IDs and titles * of the search results are logged to Apps Script's log. * @@ -41,10 +18,9 @@ function searchByKeyword() { } -// TITLE: Retrieve my uploads -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.channels.list /** + * Title: Retrieve my uploads + * Method: youtube.channels.list * This function retrieves the current script user's uploaded videos. To execute, * it requires the OAuth read/write scope for YouTube as well as user authorization. * In Apps Script's runtime environment, the first time a user runs a script, Apps @@ -93,10 +69,9 @@ function retrieveMyUploads() { } } -// TITLE: Update video -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.videos.update /** + * Title: Update video + * Method: youtube.videos.update * This sample finds the active user's uploads, then updates the most recent * upload's description by appending a string. */ @@ -133,10 +108,9 @@ function updateVideo() { } } -// TITLE: Subscribe to channel -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.subscriptions.insert /** + * Title: Subscribe to channel + * Method: youtube.subscriptions.insert * This sample subscribes the active user to the GoogleDevelopers * YouTube channel, specified by the channelId. */ @@ -165,10 +139,9 @@ function addSubscription() { } } -// TITLE: Post channel bulletin -// DESCRIPTION: use_function_comment -// API_METHOD: youtube.activities.insert /** + * Title: Post channel bulletin + * Method: youtube.activities.insert * This function creates and posts a new channel bulletin, adding a video and message. Note that this * will also accept a playlist ID. After completing the API call, logs the output to the log. */ @@ -194,10 +167,9 @@ function postChannelBulletin() { Logger.log(response); } -// TITLE: Export YouTube Analytics data to Google Sheets -// DESCRIPTION: use_function_comment -// API_METHOD: youtubeAnalytics.reports.query /** + * Title: Export YouTube Analytics data to Google Sheets + * Method: youtubeAnalytics.reports.query * This function uses the YouTube Analytics API to fetch data about the * authenticated user's channel, creating a new Google Sheet in the user's Drive * with the data. diff --git a/dotnet/MyUploads.cs b/dotnet/MyUploads.cs index 7b1b1976..01a22314 100644 --- a/dotnet/MyUploads.cs +++ b/dotnet/MyUploads.cs @@ -1,5 +1,3 @@ -/* - using System; using System.IO; using System.Reflection; diff --git a/dotnet/PlaylistUpdates.cs b/dotnet/PlaylistUpdates.cs index a2fe769b..f8e27fd4 100644 --- a/dotnet/PlaylistUpdates.cs +++ b/dotnet/PlaylistUpdates.cs @@ -1,5 +1,3 @@ -/* - using System; using System.IO; using System.Reflection; diff --git a/dotnet/README.md b/dotnet/README.md new file mode 100644 index 00000000..3d9a8932 --- /dev/null +++ b/dotnet/README.md @@ -0,0 +1,27 @@ +## Samples in this directory: + +### [Create a playlist](/dotnet/PlaylistUpdates.cs) + +Method: youtube.playlists.insert
+Description: The following code sample calls the API's playlists.insert method to create a private playlist +owned by the channel authorizing the request. + +### [Retrieve my uploads](/dotnet/MyUploads.cs) + +Method: youtube.playlistItems.list
+Description: The following code sample calls the API's playlistItems.list method to retrieve a list of videos +uploaded to the channel associated with the request. The code also calls the channels.list method with the +mine parameter set to true to retrieve the playlist ID that identifies the channel's uploaded +videos. + +### [Search by keyword](/dotnet/Search.cs) + +Method: youtube.search.list
+Description: The following code sample calls the API's search.list method to retrieve search results +associated with a particular keyword. + +### [Upload a video](/dotnet/UploadVideo.cs) + +Method: youtube.videos.insert
+Description: The following code sample calls the API's videos.insert method to upload a video to the channel +associated with the request. diff --git a/dotnet/Search.cs b/dotnet/Search.cs index a31a3eff..c84357f3 100644 --- a/dotnet/Search.cs +++ b/dotnet/Search.cs @@ -1,5 +1,3 @@ -/* - using System; using System.Collections.Generic; using System.IO; diff --git a/dotnet/UploadVideo.cs b/dotnet/UploadVideo.cs index f6d7b893..0e718b37 100644 --- a/dotnet/UploadVideo.cs +++ b/dotnet/UploadVideo.cs @@ -1,5 +1,3 @@ -/* - using System; using System.IO; using System.Reflection; diff --git a/go/README b/go/README deleted file mode 100644 index c64bde18..00000000 --- a/go/README +++ /dev/null @@ -1,36 +0,0 @@ -Minimum Go version: go 1.1+ - -To run these code samples, you will need to install the dependent libraries via -the "go get" command. These code samples require the goauth2 and google-api-go-client -libraries which can be installed with the following commands: - - go get code.google.com/p/goauth2/oauth - go get code.google.com/p/google-api-go-client/youtube/v3 - -The keyword search and topic search samples can be run via the standard "go run" command -once the developerKey constant is populated with an API key created at -https://code.google.com/apis/console. - -Example usage: - - go run search_key_keyword.go - -The Freebase API and YouTube Data APIs must be enabled for the project associated with this -key. - -To run any sample that requires authorization on behalf of a user, such as checking -for uploads, this requires the shared oauth.go file to be passed as a parameter to "go run". -These samples require a "Web Application" client ID and client secret pair which can -also be created at the Google API console at https://code.google.com/apis/console. Once -a client ID and secret pair have been created, these values must be populated into -client_secrets.json in the corresponding fields. A template client_secrets.json has been -provided in client_secrets.json.sample. Rename this file and populate the fields. - -Example usage: - - go run my_uploads.go oauth.go - -oauth.go contains code that is shared between the code samples that require OAuth 2.0 -authorization, so it must be passed as a parameter to "go run". - -More information about the YouTube APIs can be found at https://developer.google.com/youtube. diff --git a/go/README.md b/go/README.md new file mode 100644 index 00000000..f85a7847 --- /dev/null +++ b/go/README.md @@ -0,0 +1,81 @@ +Recommended Go version: latest version + +To run these code samples, you will need to install the dependent libraries via +the "go get" command. See the client library's getting started guide for more detail: +https://github.com/google/google-api-go-client/blob/master/GettingStarted.md + +You also need to enable the YouTube Data API for the project associated with your developer +credentials. + +## Authorization credentials +To run any sample that does not require user authorization, such as search\_by\_keyword.go, +you need to replace the value of the `developerKey` constant with a valid API key: + +``` +const developerKey = "YOUR DEVELOPER KEY" +``` + +To run any sample that requires authorization on behalf of a user, such as retrieving the +authenticated user's uploads, you need an OAuth 2.0 client ID and client secret pair. These +can be created at the Google API console at https://developers.google.com/console. After +creating your OAuth 2.0 credentials, download the client\_secret.json file to the directory +in which you are running these samples. + +## Running samples + +Samples can be run with the standard "go run" command as long as your API key or OAuth 2.0 +credentials are in place. The samples use the `errors.go` file to +print out API errors, so you need to also include that file in the "go run" command. Samples +that require authorization also require the `oauth2.go` file to be included in the +"go run" command: + +Example usages: + +``` +   go run search_by_keyword.go errors.go + go run my_uploads.go errors.go oauth2.go + go run upload_video.go errors.go oauth2.go --filename="sample_video.flv" --title="Test video" --keywords="golang test" +``` + +More information about the YouTube APIs can be found at https://developers.google.com/youtube. + +## Samples in this directory: + +### [Authorize a request](/go/oauth2.go) + +Description: This code sample performs OAuth 2.0 authorization by checking for the presence of a local file that +contains authorization credentials. If the file is not present, the script opens a browser and waits for a response, +then saves the returned credentials locally. + +### [List playlists](/go/playlists.go) + +Methods: youtube.playlists.list
+Description: This code sample calls the API's `playlists.list` method. Use command-line flags to define the parameters you want to use in the request as shown in the following examples:

+ +``` +# Retrieve playlists for a specified channel +go run playlists.go oauth.go errors.go --channelId=UC_x5XG1OV2P6uZZ5FSM9Ttw + +# Retrieve authenticated user's playlists +go run playlists.go oauth.go errors.go --mine=true +``` + +### [Retrieve my uploads](/go/my_uploads.go) + +Methods: youtube.channels.list, youtube.playlistItems.list
+Description: This code sample calls the API's playlistItems.list method to retrieve a list of +videos uploaded to the channel associated with the request. The code also calls the channels.list +method with the mine parameter set to true to retrieve the playlist ID that identifies +the channel's uploaded videos. + +### [Search by keyword](/go/search_by_keyword.go) + +Method: youtube.search.list
+Description: This code sample calls the API's search.list method to retrieve search results associated +with a particular keyword. + +### [Upload a video](/go/upload_video.go) + +Method: youtube.videos.insert
+Description: This code sample calls the API's videos.insert method to upload a video to the channel +associated with the request. diff --git a/go/errors.go b/go/errors.go new file mode 100644 index 00000000..a53fa3e4 --- /dev/null +++ b/go/errors.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" +) + +func handleError(err error, message string) { + if message == "" { + message = "Error making API call" + } + if err != nil { + log.Fatalf(message + ": %v", err.Error()) + } +} diff --git a/go/my_uploads.go b/go/my_uploads.go index cdddbf87..216ce3cf 100644 --- a/go/my_uploads.go +++ b/go/my_uploads.go @@ -1,60 +1,54 @@ package main import ( - "flag" "fmt" "log" - "code.google.com/p/google-api-go-client/youtube/v3" + "google.golang.org/api/youtube/v3" ) -func main() { - flag.Parse() - - client, err := buildOAuthHTTPClient(youtube.YoutubeReadonlyScope) - if err != nil { - log.Fatalf("Error building OAuth client: %v", err) +// Retrieve playlistItems in the specified playlist +func playlistItemsList(service *youtube.Service, part string, playlistId string, pageToken string) *youtube.PlaylistItemListResponse { + call := service.PlaylistItems.List(part) + call = call.PlaylistId(playlistId) + if pageToken != "" { + call = call.PageToken(pageToken) } + response, err := call.Do() + handleError(err, "") + return response +} +// Retrieve resource for the authenticated user's channel +func channelsListMine(service *youtube.Service, part string) *youtube.ChannelListResponse { + call := service.Channels.List(part) + call = call.Mine(true) + response, err := call.Do() + handleError(err, "") + return response +} + +func main() { + client := getClient(youtube.YoutubeReadonlyScope) service, err := youtube.New(client) + if err != nil { log.Fatalf("Error creating YouTube client: %v", err) } - // Start making YouTube API calls. - // Call the channels.list method. Set the mine parameter to true to - // retrieve the playlist ID for uploads to the authenticated user's - // channel. - call := service.Channels.List("contentDetails").Mine(true) - - response, err := call.Do() - if err != nil { - // The channels.list method call returned an error. - log.Fatalf("Error making API call to list channels: %v", err.Error()) - } + response := channelsListMine(service, "contentDetails") for _, channel := range response.Items { playlistId := channel.ContentDetails.RelatedPlaylists.Uploads + // Print the playlist ID for the list of uploaded videos. fmt.Printf("Videos in list %s\r\n", playlistId) nextPageToken := "" for { - // Call the playlistItems.list method to retrieve the - // list of uploaded videos. Each request retrieves 50 - // videos until all videos have been retrieved. - playlistCall := service.PlaylistItems.List("snippet"). - PlaylistId(playlistId). - MaxResults(50). - PageToken(nextPageToken) - - playlistResponse, err := playlistCall.Do() - - if err != nil { - // The playlistItems.list method call returned an error. - log.Fatalf("Error fetching playlist items: %v", err.Error()) - } - + // Retrieve next set of items in the playlist. + playlistResponse := playlistItemsList(service, "snippet", playlistId, nextPageToken) + for _, playlistItem := range playlistResponse.Items { title := playlistItem.Snippet.Title videoId := playlistItem.Snippet.ResourceId.VideoId diff --git a/go/oauth.go b/go/oauth.go deleted file mode 100644 index 58ff987b..00000000 --- a/go/oauth.go +++ /dev/null @@ -1,187 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "flag" - "fmt" - "io/ioutil" - "net" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - - "code.google.com/p/goauth2/oauth" -) - -const missingClientSecretsMessage = ` -Please configure OAuth 2.0 - -To make this sample run, you need to populate the client_secrets.json file -found at: - - %v - -with information from the {{ Google Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -` - -var ( - clientSecretsFile = flag.String("secrets", "client_secrets.json", "Client Secrets configuration") - cacheFile = flag.String("cache", "request.token", "Token cache file") -) - -// ClientConfig is a data structure definition for the client_secrets.json file. -// The code unmarshals the JSON configuration file into this structure. -type ClientConfig struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - RedirectURIs []string `json:"redirect_uris"` - AuthURI string `json:"auth_uri"` - TokenURI string `json:"token_uri"` -} - -// Config is a root-level configuration object. -type Config struct { - Installed ClientConfig `json:"installed"` - Web ClientConfig `json:"web"` -} - -// openURL opens a browser window to the specified location. -// This code originally appeared at: -// http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go -func openURL(url string) error { - var err error - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("Cannot open URL %s on this platform", url) - } - return err -} - -// readConfig reads the configuration from clientSecretsFile. -// It returns an oauth configuration object for use with the Google API client. -func readConfig(scope string) (*oauth.Config, error) { - // Read the secrets file - data, err := ioutil.ReadFile(*clientSecretsFile) - if err != nil { - pwd, _ := os.Getwd() - fullPath := filepath.Join(pwd, *clientSecretsFile) - return nil, fmt.Errorf(missingClientSecretsMessage, fullPath) - } - - cfg := new(Config) - err = json.Unmarshal(data, &cfg) - if err != nil { - return nil, err - } - - var redirectUri string - if len(cfg.Web.RedirectURIs) > 0 { - redirectUri = cfg.Web.RedirectURIs[0] - } else if len(cfg.Installed.RedirectURIs) > 0 { - redirectUri = cfg.Installed.RedirectURIs[0] - } else { - return nil, errors.New("Must specify a redirect URI in config file or when creating OAuth client") - } - - return &oauth.Config{ - ClientId: cfg.Installed.ClientID, - ClientSecret: cfg.Installed.ClientSecret, - Scope: scope, - AuthURL: cfg.Installed.AuthURI, - TokenURL: cfg.Installed.TokenURI, - RedirectURL: redirectUri, - TokenCache: oauth.CacheFile(*cacheFile), - // Get a refresh token so we can use the access token indefinitely - AccessType: "offline", - // If we want a refresh token, we must set this attribute - // to force an approval prompt or the code won't work. - ApprovalPrompt: "force", - }, nil -} - -// startWebServer starts a web server that listens on http://localhost:8080. -// The webserver waits for an oauth code in the three-legged auth flow. -func startWebServer() (codeCh chan string, err error) { - listener, err := net.Listen("tcp", "localhost:8080") - if err != nil { - return nil, err - } - codeCh = make(chan string) - go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - code := r.FormValue("code") - codeCh <- code // send code to OAuth flow - listener.Close() - w.Header().Set("Content-Type", "text/plain") - fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code) - })) - - return codeCh, nil -} - -// buildOAuthHTTPClient takes the user through the three-legged OAuth flow. -// It opens a browser in the native OS or outputs a URL, then blocks until -// the redirect completes to the /oauth2callback URI. -// It returns an instance of an HTTP client that can be passed to the -// constructor of the YouTube client. -func buildOAuthHTTPClient(scope string) (*http.Client, error) { - config, err := readConfig(scope) - if err != nil { - msg := fmt.Sprintf("Cannot read configuration file: %v", err) - return nil, errors.New(msg) - } - - transport := &oauth.Transport{Config: config} - - // Try to read the token from the cache file. - // If an error occurs, do the three-legged OAuth flow because - // the token is invalid or doesn't exist. - token, err := config.TokenCache.Token() - if err != nil { - // Start web server. - // This is how this program receives the authorization code - // when the browser redirects. - codeCh, err := startWebServer() - if err != nil { - return nil, err - } - - // Open url in browser - url := config.AuthCodeURL("") - err = openURL(url) - if err != nil { - fmt.Println("Visit the URL below to get a code.", - " This program will pause until the site is visted.") - } else { - fmt.Println("Your browser has been opened to an authorization URL.", - " This program will resume once authorization has been provided.\n") - } - fmt.Println(url) - - // Wait for the web server to get the code. - code := <-codeCh - - // This code caches the authorization code on the local - // filesystem, if necessary, as long as the TokenCache - // attribute in the config is set. - token, err = transport.Exchange(code) - if err != nil { - return nil, err - } - } - - transport.Token = token - return transport.Client(), nil -} diff --git a/go/oauth2.go b/go/oauth2.go new file mode 100644 index 00000000..a612999c --- /dev/null +++ b/go/oauth2.go @@ -0,0 +1,223 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "os" + "os/exec" + "os/user" + "path/filepath" + "runtime" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +// This variable indicates whether the script should launch a web server to +// initiate the authorization flow or just display the URL in the terminal +// window. Note the following instructions based on this setting: +// * launchWebServer = true +// 1. Use OAuth2 credentials for a web application +// 2. Define authorized redirect URIs for the credential in the Google APIs +// Console and set the RedirectURL property on the config object to one +// of those redirect URIs. For example: +// config.RedirectURL = "http://localhost:8090" +// 3. In the startWebServer function below, update the URL in this line +// to match the redirect URI you selected: +// listener, err := net.Listen("tcp", "localhost:8090") +// The redirect URI identifies the URI to which the user is sent after +// completing the authorization flow. The listener then captures the +// authorization code in the URL and passes it back to this script. +// * launchWebServer = false +// 1. Use OAuth2 credentials for an installed application. (When choosing +// the application type for the OAuth2 client ID, select "Other".) +// 2. Set the redirect URI to "urn:ietf:wg:oauth:2.0:oob", like this: +// config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob" +// 3. When running the script, complete the auth flow. Then copy the +// authorization code from the browser and enter it on the command line. +const launchWebServer = false + +const missingClientSecretsMessage = ` +Please configure OAuth 2.0 +To make this sample run, you need to populate the client_secrets.json file +found at: + %v +with information from the {{ Google Cloud Console }} +{{ https://cloud.google.com/console }} +For more information about the client_secrets.json file format, please visit: +https://developers.google.com/api-client-library/python/guide/aaa_client_secrets +` + +// getClient uses a Context and Config to retrieve a Token +// then generate a Client. It returns the generated Client. +func getClient(scope string) *http.Client { + ctx := context.Background() + + b, err := ioutil.ReadFile("client_secret.json") + if err != nil { + log.Fatalf("Unable to read client secret file: %v", err) + } + + // If modifying the scope, delete your previously saved credentials + // at ~/.credentials/youtube-go.json + config, err := google.ConfigFromJSON(b, scope) + if err != nil { + log.Fatalf("Unable to parse client secret file to config: %v", err) + } + + // Use a redirect URI like this for a web app. The redirect URI must be a + // valid one for your OAuth2 credentials. + config.RedirectURL = "http://localhost:8090" + // Use the following redirect URI if launchWebServer=false in oauth2.go + // config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob" + + cacheFile, err := tokenCacheFile() + if err != nil { + log.Fatalf("Unable to get path to cached credential file. %v", err) + } + tok, err := tokenFromFile(cacheFile) + if err != nil { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + if launchWebServer { + fmt.Println("Trying to get token from web") + tok, err = getTokenFromWeb(config, authURL) + } else { + fmt.Println("Trying to get token from prompt") + tok, err = getTokenFromPrompt(config, authURL) + } + if err == nil { + saveToken(cacheFile, tok) + } + } + return config.Client(ctx, tok) +} + +// startWebServer starts a web server that listens on http://localhost:8080. +// The webserver waits for an oauth code in the three-legged auth flow. +func startWebServer() (codeCh chan string, err error) { + listener, err := net.Listen("tcp", "localhost:8090") + if err != nil { + return nil, err + } + codeCh = make(chan string) + + go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + code := r.FormValue("code") + codeCh <- code // send code to OAuth flow + listener.Close() + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code) + })) + + return codeCh, nil +} + +// openURL opens a browser window to the specified location. +// This code originally appeared at: +// http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go +func openURL(url string) error { + var err error + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("Cannot open URL %s on this platform", url) + } + return err +} + +// Exchange the authorization code for an access token +func exchangeToken(config *oauth2.Config, code string) (*oauth2.Token, error) { + tok, err := config.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatalf("Unable to retrieve token %v", err) + } + return tok, nil +} + +// getTokenFromPrompt uses Config to request a Token and prompts the user +// to enter the token on the command line. It returns the retrieved Token. +func getTokenFromPrompt(config *oauth2.Config, authURL string) (*oauth2.Token, error) { + var code string + fmt.Printf("Go to the following link in your browser. After completing " + + "the authorization flow, enter the authorization code on the command " + + "line: \n%v\n", authURL) + + if _, err := fmt.Scan(&code); err != nil { + log.Fatalf("Unable to read authorization code %v", err) + } + fmt.Println(authURL) + return exchangeToken(config, code) +} + +// getTokenFromWeb uses Config to request a Token. +// It returns the retrieved Token. +func getTokenFromWeb(config *oauth2.Config, authURL string) (*oauth2.Token, error) { + codeCh, err := startWebServer() + if err != nil { + fmt.Printf("Unable to start a web server.") + return nil, err + } + + err = openURL(authURL) + if err != nil { + log.Fatalf("Unable to open authorization URL in web server: %v", err) + } else { + fmt.Println("Your browser has been opened to an authorization URL.", + " This program will resume once authorization has been provided.\n") + fmt.Println(authURL) + } + + // Wait for the web server to get the code. + code := <-codeCh + return exchangeToken(config, code) +} + +// tokenCacheFile generates credential file path/filename. +// It returns the generated credential path/filename. +func tokenCacheFile() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") + os.MkdirAll(tokenCacheDir, 0700) + return filepath.Join(tokenCacheDir, + url.QueryEscape("youtube-go.json")), err +} + +// tokenFromFile retrieves a Token from a given file path. +// It returns the retrieved Token and any read error encountered. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + t := &oauth2.Token{} + err = json.NewDecoder(f).Decode(t) + defer f.Close() + return t, err +} + +// saveToken uses a file path to create a file and store the +// token in it. +func saveToken(file string, token *oauth2.Token) { + fmt.Println("trying to save token") + fmt.Printf("Saving credential file to: %s\n", file) + f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Unable to cache oauth token: %v", err) + } + defer f.Close() + json.NewEncoder(f).Encode(token) +} diff --git a/go/playlists.go b/go/playlists.go new file mode 100644 index 00000000..2769a582 --- /dev/null +++ b/go/playlists.go @@ -0,0 +1,72 @@ +package main + +import ( + "flag" + "fmt" + "log" + + "google.golang.org/api/youtube/v3" +) + +var ( + method = flag.String("method", "list", "The API method to execute. (List is the only method that this sample currently supports.") + + channelId = flag.String("channelId", "", "Retrieve playlists for this channel. Value is a YouTube channel ID.") + hl = flag.String("hl", "", "Retrieve localized resource metadata for the specified application language.") + maxResults = flag.Int64("maxResults", 5, "The maximum number of playlist resources to include in the API response.") + mine = flag.Bool("mine", false, "List playlists for authenticated user's channel. Default: false.") + onBehalfOfContentOwner = flag.String("onBehalfOfContentOwner", "", "Indicates that the request's auth credentials identify a user authorized to act on behalf of the specified content owner.") + pageToken = flag.String("pageToken", "", "Token that identifies a specific page in the result set that should be returned.") + part = flag.String("part", "snippet", "Comma-separated list of playlist resource parts that API response will include.") + playlistId = flag.String("playlistId", "", "Retrieve information about this playlist.") +) + +func playlistsList(service *youtube.Service, part string, channelId string, hl string, maxResults int64, mine bool, onBehalfOfContentOwner string, pageToken string, playlistId string) *youtube.PlaylistListResponse { + call := service.Playlists.List(part) + if channelId != "" { + call = call.ChannelId(channelId) + } + if hl != "" { + call = call.Hl(hl) + } + call = call.MaxResults(maxResults) + if mine != false { + call = call.Mine(true) + } + if onBehalfOfContentOwner != "" { + call = call.OnBehalfOfContentOwner(onBehalfOfContentOwner) + } + if pageToken != "" { + call = call.PageToken(pageToken) + } + if playlistId != "" { + call = call.Id(playlistId) + } + response, err := call.Do() + handleError(err, "") + return response +} + +func main() { + flag.Parse() + + if *channelId == "" && *mine == false && *playlistId == "" { + log.Fatalf("You must either set a value for the channelId or playlistId flag or set the mine flag to 'true'.") + } + client := getClient(youtube.YoutubeReadonlyScope) + + service, err := youtube.New(client) + if err != nil { + log.Fatalf("Error creating YouTube client: %v", err) + } + + response := playlistsList(service, "snippet,contentDetails", *channelId, *hl, *maxResults, *mine, *onBehalfOfContentOwner, *pageToken, *playlistId) + + for _, playlist := range response.Items { + playlistId := playlist.Id + playlistTitle := playlist.Snippet.Title + + // Print the playlist ID and title for the playlist resource. + fmt.Println(playlistId, ": ", playlistTitle) + } +} diff --git a/go/post_bulletin.go b/go/post_bulletin.go deleted file mode 100644 index e76d06c7..00000000 --- a/go/post_bulletin.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - - "code.google.com/p/google-api-go-client/youtube/v3" -) - -var ( - message = flag.String("message", "", "Text message to post") - videoID = flag.String("videoid", "", "ID of video to post") - playlistID = flag.String("playlistid", "", "ID of playlist to post") -) - -func main() { - flag.Parse() - - // A bulletin must contain a message and may also contain a video or a - // playlist. You can post a message with or without an accompanying video - // or playlist, but you can't post a video and playlist at the same time. - if *message == "" { - log.Fatalf("Please provide a message.") - } - - if *videoID != "" && *playlistID != "" { - log.Fatalf("You cannot post a video and a playlist at the same time.") - } - - client, err := buildOAuthHTTPClient(youtube.YoutubeScope) - if err != nil { - log.Fatalf("Error building OAuth client: %v", err) - } - - service, err := youtube.New(client) - if err != nil { - log.Fatalf("Error creating YouTube client: %v", err) - } - - // Start making YouTube API calls. - parts := "snippet" - bulletin := &youtube.Activity{ - Snippet: &youtube.ActivitySnippet{ - Description: *message, - }, - } - - if *videoID != "" || *playlistID != "" { - parts = "snippet,contentDetails" - - // The resource ID element value differs depending on - // whether a playlist or a video is being posted. - var resourceId *youtube.ResourceId - switch { - case *videoID != "": - resourceId = &youtube.ResourceId{ - Kind: "youtube#video", - VideoId: *videoID, - } - case *playlistID != "": - resourceId = &youtube.ResourceId{ - Kind: "youtube#playlist", - PlaylistId: *playlistID, - } - } - - bulletin.ContentDetails = &youtube.ActivityContentDetails{ - Bulletin: &youtube.ActivityContentDetailsBulletin{ - ResourceId: resourceId, - }, - } - } - - call := service.Activities.Insert(parts, bulletin) - _, err = call.Do() - if err != nil { - log.Fatalf("Error making API call to post bulletin: %v", err.Error()) - } - - fmt.Println("The bulletin was posted to your channel.") -} diff --git a/go/quickstart.go b/go/quickstart.go new file mode 100644 index 00000000..dc5d83d3 --- /dev/null +++ b/go/quickstart.go @@ -0,0 +1,140 @@ +// Sample Go code for user authorization + +package main + +import ( + "encoding/json" + "fmt" + "log" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/youtube/v3" +) + +const missingClientSecretsMessage = ` +Please configure OAuth 2.0 +` + +// getClient uses a Context and Config to retrieve a Token +// then generate a Client. It returns the generated Client. +func getClient(ctx context.Context, config *oauth2.Config) *http.Client { + cacheFile, err := tokenCacheFile() + if err != nil { + log.Fatalf("Unable to get path to cached credential file. %v", err) + } + tok, err := tokenFromFile(cacheFile) + if err != nil { + tok = getTokenFromWeb(config) + saveToken(cacheFile, tok) + } + return config.Client(ctx, tok) +} + +// getTokenFromWeb uses Config to request a Token. +// It returns the retrieved Token. +func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Go to the following link in your browser then type the "+ + "authorization code: \n%v\n", authURL) + + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatalf("Unable to read authorization code %v", err) + } + + tok, err := config.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatalf("Unable to retrieve token from web %v", err) + } + return tok +} + +// tokenCacheFile generates credential file path/filename. +// It returns the generated credential path/filename. +func tokenCacheFile() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") + os.MkdirAll(tokenCacheDir, 0700) + return filepath.Join(tokenCacheDir, + url.QueryEscape("youtube-go-quickstart.json")), err +} + +// tokenFromFile retrieves a Token from a given file path. +// It returns the retrieved Token and any read error encountered. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + t := &oauth2.Token{} + err = json.NewDecoder(f).Decode(t) + defer f.Close() + return t, err +} + +// saveToken uses a file path to create a file and store the +// token in it. +func saveToken(file string, token *oauth2.Token) { + fmt.Printf("Saving credential file to: %s\n", file) + f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Unable to cache oauth token: %v", err) + } + defer f.Close() + json.NewEncoder(f).Encode(token) +} + +func handleError(err error, message string) { + if message == "" { + message = "Error making API call" + } + if err != nil { + log.Fatalf(message + ": %v", err.Error()) + } +} + +func channelsListByUsername(service *youtube.Service, part string, forUsername string) { + call := service.Channels.List(part) + call = call.ForUsername(forUsername) + response, err := call.Do() + handleError(err, "") + fmt.Println(fmt.Sprintf("This channel's ID is %s. Its title is '%s', " + + "and it has %d views.", + response.Items[0].Id, + response.Items[0].Snippet.Title, + response.Items[0].Statistics.ViewCount)) +} + + +func main() { + ctx := context.Background() + + b, err := ioutil.ReadFile("client_secret.json") + if err != nil { + log.Fatalf("Unable to read client secret file: %v", err) + } + + // If modifying these scopes, delete your previously saved credentials + // at ~/.credentials/youtube-go-quickstart.json + config, err := google.ConfigFromJSON(b, youtube.YoutubeReadonlyScope) + if err != nil { + log.Fatalf("Unable to parse client secret file to config: %v", err) + } + client := getClient(ctx, config) + service, err := youtube.New(client) + + handleError(err, "Error creating YouTube client") + + channelsListByUsername(service, "snippet,contentDetails,statistics", "GoogleDevelopers") +} diff --git a/go/search_by_keyword.go b/go/search_by_keyword.go index 38fa7fb6..3c8f0b64 100644 --- a/go/search_by_keyword.go +++ b/go/search_by_keyword.go @@ -6,8 +6,8 @@ import ( "log" "net/http" - "code.google.com/p/google-api-go-client/googleapi/transport" - "code.google.com/p/google-api-go-client/youtube/v3" + "google.golang.org/api/googleapi/transport" + "google.golang.org/api/youtube/v3" ) var ( @@ -34,9 +34,7 @@ func main() { Q(*query). MaxResults(*maxResults) response, err := call.Do() - if err != nil { - log.Fatalf("Error making search API call: %v", err) - } + handleError(err, "") // Group video, channel, and playlist results in separate lists. videos := make(map[string]string) diff --git a/go/search_by_topic.go b/go/search_by_topic.go deleted file mode 100644 index f5be7151..00000000 --- a/go/search_by_topic.go +++ /dev/null @@ -1,173 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "flag" - "fmt" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - - "code.google.com/p/google-api-go-client/googleapi/transport" - "code.google.com/p/google-api-go-client/youtube/v3" -) - -var ( - query = flag.String("query", "Google", "Freebase search term") - maxResults = flag.Int64("max-results", 25, "Max YouTube results") - resultType = flag.String("type", "channel", "YouTube result type: video, playlist, or channel") -) - -const developerKey = "YOUR DEVELOPER KEY HERE" -const freebaseSearchURL = "https://www.googleapis.com/freebase/v1/search?%s" - -// Notable is struct for unmarshalling JSON values from the API. -type Notable struct { - Name string - ID string -} - -// FreebaseTopic is struct for unmarshalling JSON values from the API. -type FreebaseTopic struct { - Mid string - ID string - Name string - Notable Notable - Lang string - Score float64 -} - -// FreebaseResponse is struct for unmarshalling JSON values from the Freebase API. -type FreebaseResponse struct { - Status string - Result []FreebaseTopic -} - -func main() { - flag.Parse() - - topicID, err := getTopicID(*query) - if err != nil { - log.Fatalf("Cannot fetch topic ID from Freebase: %v", err) - } - - err = youtubeSearch(topicID) - if err != nil { - log.Fatalf("Cannot make YouTube API call: %v", err) - } -} - -// getTopicID queries Freebase with the given string. It then prompts the user -// to select a topic, then returns the selected topic so that the topic can be -// used to search YouTube for videos, channels or playlists. -func getTopicID(topic string) (string, error) { - urlParams := url.Values{ - "query": []string{topic}, - "key": []string{developerKey}, - } - - apiURL := fmt.Sprintf(freebaseSearchURL, urlParams.Encode()) - - resp, err := http.Get(apiURL) - if err != nil { - return "", err - } else if resp.StatusCode != http.StatusOK { - errorMsg := fmt.Sprintf("Received HTTP status code %v using developer key: %v", - resp.StatusCode, developerKey) - return "", errors.New(errorMsg) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - - var data FreebaseResponse - err = json.Unmarshal(body, &data) - if err != nil { - return "", nil - } - - if len(data.Result) == 0 { - return "", errors.New("No matching terms were found in Freebase.") - } - - // Print a list of topics for the user to select. - fmt.Println("The following topics were found:") - for index, topic := range data.Result { - if topic.Notable.Name == "" { - topic.Notable.Name = "Unknown" - } - fmt.Printf(" %2d. %s (%s)\r\n", index+1, topic.Name, topic.Notable.Name) - } - - prompt := fmt.Sprintf("Enter a topic number to find related YouTube %s [1-%v]: ", - *resultType, len(data.Result)) - selection, err := readInt(prompt, 1, len(data.Result)) - if err != nil { - return "", nil - } - choice := data.Result[selection-1] - return choice.Mid, nil -} - -// readInt reads an integer from standard input and verifies that the value -// is between the allowed min and max values (inclusive). -func readInt(prompt string, min int, max int) (int, error) { - // Loop until we have a valid input. - for { - fmt.Print(prompt) - var i int - _, err := fmt.Fscan(os.Stdin, &i) - if err != nil { - return 0, err - } - if i < min || i > max { - fmt.Println("Invalid input.") - continue - } - return i, nil - } -} - -// youtubeSearch searches YouTube for the topic given in the query flag and -// prints the results. This function takes a mid parameter, which specifies -// a value retrieved using the Freebase API. -func youtubeSearch(mid string) error { - client := &http.Client{ - Transport: &transport.APIKey{Key: developerKey}, - } - - service, err := youtube.New(client) - if err != nil { - return err - } - - // Make the API call to YouTube. - call := service.Search.List("id,snippet"). - TopicId(mid). - Type(*resultType). - MaxResults(*maxResults) - response, err := call.Do() - if err != nil { - return err - } - - // Iterate through each item and output it. - for _, item := range response.Items { - itemID := "" - switch item.Id.Kind { - case "youtube#video": - itemID = item.Id.VideoId - case "youtube#channel": - itemID = item.Id.ChannelId - case "youtube#playlist": - itemID = item.Id.PlaylistId - } - fmt.Printf("%v (%v)\r\n", item.Snippet.Title, itemID) - } - return nil -} diff --git a/go/upload_video.go b/go/upload_video.go index 04d3ed5f..24972c63 100644 --- a/go/upload_video.go +++ b/go/upload_video.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "code.google.com/p/google-api-go-client/youtube/v3" + "google.golang.org/api/youtube/v3" ) var ( @@ -26,10 +26,7 @@ func main() { log.Fatalf("You must provide a filename of a video file to upload") } - client, err := buildOAuthHTTPClient(youtube.YoutubeUploadScope) - if err != nil { - log.Fatalf("Error building OAuth client: %v", err) - } + client := getClient(youtube.YoutubeUploadScope) service, err := youtube.New(client) if err != nil { @@ -59,8 +56,6 @@ func main() { } response, err := call.Media(file).Do() - if err != nil { - log.Fatalf("Error making YouTube API call: %v", err) - } + handleError(err, "") fmt.Printf("Upload successful! Video ID: %v\n", response.Id) } diff --git a/java/README b/java/README deleted file mode 100644 index b71843bc..00000000 --- a/java/README +++ /dev/null @@ -1,21 +0,0 @@ -Prerequisites for this code sample: -- Java 1.6 -- Apache Maven (http://maven.apache.org) - -Before running the sample, client_secrets.json must be populated with a -client ID and client secret. You can create an ID/secret pair at: - - https://code.google.com/apis/console - -To build this code sample from the command line, type: - - mvn compile - -To run the code sample from the command line, enter the following: - - mvn exec:java -Dexec.mainClass="FULL_CLASS_NAME" - -For more instructions about how to set up Maven and/or your IDE to run -YouTube API samples, see this video: - - http://youtu.be/pb_t5_ShQOM diff --git a/java/README.md b/java/README.md new file mode 100644 index 00000000..d9baaf89 --- /dev/null +++ b/java/README.md @@ -0,0 +1,267 @@ +Prerequisites for this code sample: +- Java 1.6 +- Apache Maven (http://maven.apache.org) + +Before running the sample, client_secrets.json must be populated with a +client ID and client secret. You can create an ID/secret pair at: + + https://code.google.com/apis/console + +To build this code sample from the command line, type: + + mvn compile + +To run a code sample from the command line, enter the following: + + mvn exec:java -Dexec.mainClass="FULL_CLASS_NAME" + +For samples that require arguments, also specify -Dexec.args, e.g.: + + mvn exec:java -Dexec.mainClass="FULL_CLASS_NAME" -Dexec.args="arg1 arg2" + +For more instructions about how to set up Maven and/or your IDE to run +YouTube API samples, see this video: + + http://youtu.be/pb_t5_ShQOM + +## Samples in this directory: + +### [Authorize a request](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/Auth.java) + +Description: This sample demonstrates how to use OAuth 2.0 to authorize an application to access resources on a user's behalf. + +### [Add a channel subscription](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/AddSubscription.java) + +Method: youtube.subscriptions.insert
+Description: This sample calls the API's subscriptions.insert method to add a subscription to a specified +channel. + +### [Add a featured video](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/InvideoProgramming.java) + +Method: youtube.channels.update
+Description: This sample calls the API's channels.update method to set invideoPromotion +properties for the channel. + +### [Create a playlist](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/PlaylistUpdates.java) + +Method: youtube.playlists.insert
+Description: This sample calls the API's playlists.insert method to create a private playlist owned by the +channel authorizing the request. + +### [Create and manage comments](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentHandling.java) + +Method: youtube.commentThreads.list, youtube.comments.insert, youtube.comments.list, youtube.comments.update, +youtube.comments.setModerationStatus, youtube.comments.markAsSpam, youtube.comments.delete
+Description: This sample demonstrates how to use the following API methods to create and manage comments:
+ + +### [Create and manage comment threads](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentThreads.java) + +Method: youtube.commentThreads.insert, youtube.commentThreads.list, youtube.commentThreads.update
+Description: This sample demonstrates how to use the following API methods to create and manage top-level comments:
+ + +### [Create and manage YouTube video caption tracks](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Captions.java) + +Method: youtube.captions.insert, youtube.captions.list, youtube.captions.update, youtube.captions.download, +youtube.captions.delete
+Description: This sample demonstrates how to use the following API methods to create and manage YouTube video caption +tracks:
+ + +### [Post a channel bulletin](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/ChannelBulletin.java) + +Method: youtube.activities.insert
+Description: This sample calls the API's activities.insert method to post a bulletin to the channel +associated with the request. + +### [Retrieve my uploads](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/MyUploads.java) + +Method: youtube.playlistItems.list
+Description: This sample calls the API's playlistItems.list method to retrieve a list of videos uploaded +to the channel associated with the request. The code also calls the channels.list method with the +mine parameter set to true to retrieve the playlist ID that identifies the channel's uploaded +videos. + +### [Search by keyword](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Search.java) + +Method: youtube.search.list
+Description: This sample calls the API's search.list method to retrieve search results associated with +a particular keyword. + +### [Search by location](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/GeolocationSearch.java) + +Method: youtube.search.list, youtube.videos.list
+Description: This sample calls the API's search.list method with the type, q, location, and +locationRadius parameters to retrieve search results matching the provided keyword within the radius centered +at a particular location. Using the video IDs from the search result, the sample calls the API's videos.list +method to retrieve location details of each video. + +### [Set and retrieve localized channel metadata](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/ChannelLocalizations.java) + +Method: youtube.channels.update, youtube.channels.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel:
+ + +### [Set and retrieve localized channel section metadata](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/ChannelSectionLocalizations.java) + +Method: youtube.channelSections.update, youtube.channelSections.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel section:
+ + +### [Set and retrieve localized playlist metadata](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/PlaylistLocalizations.java) + +Method: youtube.playlists.update, youtube.playlists.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +playlist:
+ + +### [Set and retrieve localized video metadata](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/VideoLocalizations.java) + +Method: youtube.videos.update, youtube.videos.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata +for a video:
+ + +### [Update a video](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/UpdateVideo.java) + +Method: youtube.videos.update
+Description: This sample calls the API's videos.update method to update a video owned by the channel +authorizing the request. + +### [Upload a custom video thumbnail image](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/UploadThumbnail.java) + +Method: youtube.thumbnails.set
+Description: This sample calls the API's thumbnails.set method to upload an image and set it as the +thumbnail image for a video. The request must be authorized by the channel that owns the video. + +### [Upload a video](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/UploadVideo.java) + +Method: youtube.videos.insert
+Description: This sample calls the API's videos.insert method to upload a video to the channel associated +with the request. + +### [Retrieve top 10 videos by viewcount](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/analytics/YouTubeAnalyticsReports.java) + +Method: youtubeAnalytics.reports.query
+Description: This sample calls the API's reports.query method to retrieve YouTube Analytics data. +By default, the report retrieves the top 10 videos based on viewcounts, and it returns several metrics for those +videos, sorting the results in reverse order by viewcount. By setting command line parameters, you can use the +same code to retrieve other reports as well. + +### [Create a reporting job](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/CreateReportingJob.java) + +Method: youtubeReporting.reportTypes.list, youtubeReporting.jobs.create
+Description: This sample demonstrates how to create a reporting job. It calls the reportTypes.list method +to retrieve a list of available report types. It then calls the jobs.create method to create a new reporting +job. + +### [Retrieve reports](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/RetrieveReports.java) + +Method: youtubeReporting.jobs.list, youtubeReporting.reports.list
+Description: This sample demonstrates how to retrieve reports created by a specific job. It calls the +jobs.list method to retrieve reporting jobs. It then calls the reports.list method with the +jobId parameter set to a specific job ID to retrieve reports created by that job. Finally, the sample +prints out the download URL for each report. + +### [Create a broadcast and stream](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/CreateBroadcast.java) + +Method: youtube.liveBroadcasts.bind,youtube.liveBroadcasts.insert,youtube.liveStreams.insert
+Description: This sample calls the API's liveBroadcasts.insert and liveStreams.insert +methods to create a broadcast and a stream. Then, it calls the liveBroadcasts.bind method to bind +the stream to the broadcast. + +### [Retrieve a channel's broadcasts](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListBroadcasts.java) + +Method: youtube.liveBroadcasts.list
+Description: This sample calls the API's liveBroadcasts.list method to retrieve a list of broadcasts for +the channel associated with the request. By default, the request retrieves all broadcasts for the channel, but you can +also specify a value for the --broadcast-status option to only retrieve broadcasts with a particular status. + +### [Retrieve a channel's live video streams](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListStreams.java) + +Method: youtube.liveStreams.list
+Description: This sample calls the API's liveStreams.list method to retrieve a list of video stream settings +that a channel can use to broadcast live events on YouTube. + + +### [Get a live chat id](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java) + +Methods: youtube.videos.list, youtube.liveBroadcasts.list
+Description: This sample retrieves the live chat ID from either a videoId parameter +or the live broadcast for the authorized user's channel. The liveChatId is required for other samples +that interact with live chat. + +### [Insert a live chat message](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/InsertLiveChatMessage.java) + +Method: youtube.liveChatMessages.insert
+Description: This sample inserts a live chat message into the the specified video or the live broadcast for +the authorized user's channel. + +### [Delete a live chat message](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/DeleteLiveChatMessage.java) + +Method: youtube.liveChatMessages.delete
+Description: This sample deletes the specified live chat message. + +### [List live chat messages](/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListLiveChatMessages.java) + +Method: youtube.liveChatMessages.list
+Description: This sample lists live chat messages from the specified video or from the live broadcast for +the authorized user's channel. diff --git a/java/pom.xml b/java/pom.xml index 91146f20..200ffec1 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -11,10 +11,11 @@ http://maven.apache.org - v3-rev107-1.18.0-rc - v1-rev24-1.17.0-rc - 1.18.0-rc - 1.18.0-rc + v3-rev182-1.22.0 + v1-rev63-1.22.0 + v1-rev10-1.22.0 + 1.20.0 + 1.20.0 UTF-8 @@ -34,13 +35,20 @@ ${project.youtube.version} - + com.google.apis google-api-services-youtubeAnalytics ${project.youtube.analytics.version} + + + com.google.apis + google-api-services-youtubereporting + ${project.youtube.reporting.version} + + org.codehaus.jackson @@ -73,7 +81,7 @@ maven-compiler-plugin - 2.3.2 + 3.6.0 1.6 1.6 diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentHandling.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentHandling.java index 7bde57b8..75f58c36 100644 --- a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentHandling.java +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentHandling.java @@ -26,8 +26,8 @@ import com.google.api.services.youtube.model.Comment; import com.google.api.services.youtube.model.CommentSnippet; import com.google.api.services.youtube.model.CommentThread; -import com.google.api.services.youtube.model.V3CommentListResponse; -import com.google.api.services.youtube.model.V3CommentThreadListResponse; +import com.google.api.services.youtube.model.CommentListResponse; +import com.google.api.services.youtube.model.CommentThreadListResponse; import com.google.common.collect.Lists; /** @@ -86,7 +86,7 @@ public static void main(String[] args) { // Call the YouTube Data API's commentThreads.list method to // retrieve video comment threads. - V3CommentThreadListResponse videoCommentsListResponse = youtube.commentThreads() + CommentThreadListResponse videoCommentsListResponse = youtube.commentThreads() .list("snippet").setVideoId(videoId).setTextFormat("plainText").execute(); List videoComments = videoCommentsListResponse.getItems(); @@ -138,7 +138,7 @@ public static void main(String[] args) { // Call the YouTube Data API's comments.list method to retrieve // existing comment // replies. - V3CommentListResponse commentsListResponse = youtube.comments().list("snippet") + CommentListResponse commentsListResponse = youtube.comments().list("snippet") .setParentId(parentId).setTextFormat("plainText").execute(); List comments = commentsListResponse.getItems(); diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentThreads.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentThreads.java index e0e3de8d..b421da43 100644 --- a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentThreads.java +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/CommentThreads.java @@ -27,7 +27,7 @@ import com.google.api.services.youtube.model.CommentSnippet; import com.google.api.services.youtube.model.CommentThread; import com.google.api.services.youtube.model.CommentThreadSnippet; -import com.google.api.services.youtube.model.V3CommentThreadListResponse; +import com.google.api.services.youtube.model.CommentThreadListResponse; import com.google.common.collect.Lists; /** @@ -135,7 +135,7 @@ public static void main(String[] args) { // Call the YouTube Data API's commentThreads.list method to // retrieve video comment threads. - V3CommentThreadListResponse videoCommentsListResponse = youtube.commentThreads() + CommentThreadListResponse videoCommentsListResponse = youtube.commentThreads() .list("snippet").setVideoId(videoId).setTextFormat("plainText").execute(); List videoComments = videoCommentsListResponse.getItems(); @@ -172,7 +172,7 @@ public static void main(String[] args) { // Call the YouTube Data API's commentThreads.list method to // retrieve channel comment threads. - V3CommentThreadListResponse channelCommentsListResponse = youtube.commentThreads() + CommentThreadListResponse channelCommentsListResponse = youtube.commentThreads() .list("snippet").setChannelId(channelId).setTextFormat("plainText").execute(); List channelComments = channelCommentsListResponse.getItems(); diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Quickstart.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Quickstart.java new file mode 100644 index 00000000..efefe20b --- /dev/null +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Quickstart.java @@ -0,0 +1,119 @@ +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.store.FileDataStoreFactory; + +import com.google.api.services.youtube.YouTubeScopes; +import com.google.api.services.youtube.model.*; +import com.google.api.services.youtube.YouTube; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + + +public class Quickstart { + + /** Application name. */ + private static final String APPLICATION_NAME = "API Sample"; + + /** Directory to store user credentials for this application. */ + private static final java.io.File DATA_STORE_DIR = new java.io.File( + System.getProperty("user.home"), ".credentials/youtube-java-quickstart"); + + /** Global instance of the {@link FileDataStoreFactory}. */ + private static FileDataStoreFactory DATA_STORE_FACTORY; + + /** Global instance of the JSON factory. */ + private static final JsonFactory JSON_FACTORY = + JacksonFactory.getDefaultInstance(); + + /** Global instance of the HTTP transport. */ + private static HttpTransport HTTP_TRANSPORT; + + /** Global instance of the scopes required by this quickstart. + * + * If modifying these scopes, delete your previously saved credentials + * at ~/.credentials/drive-java-quickstart + */ + private static final List SCOPES = + Arrays.asList(YouTubeScopes.YOUTUBE_READONLY); + + static { + try { + HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + /** + * Create an authorized Credential object. + * @return an authorized Credential object. + * @throws IOException + */ + public static Credential authorize() throws IOException { + // Load client secrets. + InputStream in = + Quickstart.class.getResourceAsStream("/client_secret.json"); + GoogleClientSecrets clientSecrets = + GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + + // Build flow and trigger user authorization request. + GoogleAuthorizationCodeFlow flow = + new GoogleAuthorizationCodeFlow.Builder( + HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(DATA_STORE_FACTORY) + .setAccessType("offline") + .build(); + Credential credential = new AuthorizationCodeInstalledApp( + flow, new LocalServerReceiver()).authorize("user"); + return credential; + } + + /** + * Build and return an authorized API client service, such as a YouTube + * Data API client service. + * @return an authorized API client service + * @throws IOException + */ + public static YouTube getYouTubeService() throws IOException { + Credential credential = authorize(); + return new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential) + .setApplicationName(APPLICATION_NAME) + .build(); + } + + public static void main(String[] args) throws IOException { + YouTube youtube = getYouTubeService(); + try { + YouTube.Channels.List channelsListByUsernameRequest = youtube.channels().list("snippet,contentDetails,statistics"); + channelsListByUsernameRequest.setForUsername("GoogleDevelopers"); + + ChannelListResponse response = channelsListByUsernameRequest.execute(); + Channel channel = response.getItems().get(0); + System.out.printf( + "This channel's ID is %s. Its title is '%s', and it has %s views.\n", + channel.getId(), + channel.getSnippet().getTitle(), + channel.getStatistics().getViewCount()); + } catch (GoogleJsonResponseException e) { + e.printStackTrace(); + System.err.println("There was a service error: " + + e.getDetails().getCode() + " : " + e.getDetails().getMessage()); + } catch (Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Topics.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Topics.java deleted file mode 100644 index c79d823a..00000000 --- a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/data/Topics.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (c) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.api.services.samples.youtube.cmdline.data; - -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestInitializer; -import com.google.api.services.samples.youtube.cmdline.Auth; -import com.google.api.services.youtube.YouTube; -import com.google.api.services.youtube.model.ResourceId; -import com.google.api.services.youtube.model.SearchListResponse; -import com.google.api.services.youtube.model.SearchResult; -import com.google.api.services.youtube.model.Thumbnail; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.node.ArrayNode; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This application demonstrates a semantic YouTube search that prompts the - * user to enter a search term and select a topic related to that term. The - * class calls the Freebase API to get a topic ID, then passes that id along - * with another user query term to the YouTube APIs. The result is a list of - * videos based on a semantic search. - * - * @author Jeremy Walker - */ -public class Topics { - - /** - * Define a global variable that identifies the name of a file that - * contains the developer's API key. - */ - private static final String PROPERTIES_FILENAME = "youtube.properties"; - - /** - * Define a global variable that specifies the maximum number of videos - * that an API response can contain. - */ - private static final long NUMBER_OF_VIDEOS_RETURNED = 5; - - /** - * Define a global variable that specifies the maximum number of topics - * that an API response can contain. - */ - private static final long NUMBER_OF_TOPICS_RETURNED = 5; - - /** - * Define a global instance of a Youtube object, which will be used - * to make YouTube Data API requests. - */ - private static YouTube youtube; - - /** - * Execute a search request that starts by calling the Freebase API to - * retrieve a topic ID matching a user-provided term. Then initialize a - * YouTube object to search for YouTube videos and call the YouTube Data - * API's youtube.search.list method to retrieve a list of videos associated - * with the selected Freebase topic and with another search query term, - * which the user also enters. Finally, display the titles, video IDs, and - * thumbnail images for the first five videos in the YouTube Data API - * response. - * - * @param args This application does not use command line arguments. - */ - public static void main(String[] args) { - // Read the developer key from the properties file. - Properties properties = new Properties(); - try { - InputStream in = Topics.class.getResourceAsStream("/" + PROPERTIES_FILENAME); - properties.load(in); - - } catch (IOException e) { - System.err.println("There was an error reading " + PROPERTIES_FILENAME + ": " + e.getCause() - + " : " + e.getMessage()); - System.exit(1); - } - - - try { - // Retrieve a Freebase topic ID based on a user-entered query term. - String topicsId = getTopicId(); - if (topicsId.length() < 1) { - System.out.println("No topic id will be applied to your search."); - } - - // Prompt the user to enter a search query term. This term will be - // used to retrieve YouTube search results related to the topic - // selected above. - String queryTerm = getInputQuery("search"); - - // This object is used to make YouTube Data API requests. The last - // argument is required, but since we don't need anything - // initialized when the HttpRequest is initialized, we override - // the interface and provide a no-op function. - youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, new HttpRequestInitializer() { - public void initialize(HttpRequest request) throws IOException { - } - }) - .setApplicationName("youtube-cmdline-search-sample") - .build(); - - YouTube.Search.List search = youtube.search().list("id,snippet"); - - // Set your developer key from the {{ Google Cloud Console }} for - // non-authenticated requests. See: - // {{ https://cloud.google.com/console }} - String apiKey = properties.getProperty("youtube.apikey"); - search.setKey(apiKey); - search.setQ(queryTerm); - if (topicsId.length() > 0) { - search.setTopicId(topicsId); - } - - // Restrict the search results to only include videos. See: - // https://developers.google.com/youtube/v3/docs/search/list#type - search.setType("video"); - - // To increase efficiency, only retrieve the fields that the - // application uses. - search.setFields("items(id/kind,id/videoId,snippet/title,snippet/thumbnails/default/url)"); - search.setMaxResults(NUMBER_OF_VIDEOS_RETURNED); - SearchListResponse searchResponse = search.execute(); - - List searchResultList = searchResponse.getItems(); - - if (searchResultList != null) { - prettyPrint(searchResultList.iterator(), queryTerm, topicsId); - } else { - System.out.println("There were no results for your query."); - } - } catch (GoogleJsonResponseException e) { - System.err.println("There was a service error: " + e.getDetails().getCode() + - " : " + e.getDetails().getMessage()); - e.printStackTrace(); - } catch (IOException e) { - System.err.println("There was an IO error: " + e.getCause() + " : " + e.getMessage()); - e.printStackTrace(); - } - } - - /* - * Prompt the user to enter a search term and return the user's input. - * - * @param searchCategory This value is displayed to the user to clarify the information that the application is requesting. - */ - private static String getInputQuery(String searchCategory) throws IOException { - - String inputQuery = ""; - - BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in)); - - do { - System.out.print("Please enter a " + searchCategory + " term: "); - inputQuery = bReader.readLine(); - } while (inputQuery.length() < 1); - - return inputQuery; - } - - /** - * Retrieve Freebase topics that match a user-provided query term. Then - * prompt the user to select a topic and return its topic ID. - */ - private static String getTopicId() throws IOException { - - // The application will return an empty string if no matching topic ID - // is found or no results are available. - String topicsId = ""; - - // Prompt the user to enter a query term for finding Freebase topics. - String topicQuery = getInputQuery("topics"); - - // The Freebase Java library does not provide search functionality, so - // the application needs to call directly against the URL. This code - // constructs the proper URL, then uses jackson classes to convert the - // JSON response into a Java object. You can learn more about the - // Freebase search calls at: http://wiki.freebase.com/wiki/ApiSearch. - HttpClient httpclient = new DefaultHttpClient(); - List params = new ArrayList(); - params.add(new BasicNameValuePair("query", topicQuery)); - params.add(new BasicNameValuePair("limit", Long.toString(NUMBER_OF_TOPICS_RETURNED))); - - String serviceURL = "https://www.googleapis.com/freebase/v1/search"; - String url = serviceURL + "?" + URLEncodedUtils.format(params, "UTF-8"); - - HttpResponse httpResponse = httpclient.execute(new HttpGet(url)); - HttpEntity entity = httpResponse.getEntity(); - - if (entity != null) { - InputStream instream = entity.getContent(); - try { - - // Convert the JSON to an object. This code does not do an - // exact map from JSON to POJO (Plain Old Java object), but - // you could create additional classes and use them with the - // mapper.readValue() function to get that exact mapping. - ObjectMapper mapper = new ObjectMapper(); - JsonNode rootNode = mapper.readValue(instream, JsonNode.class); - - // Confirm that the HTTP request was handled successfully by - // checking the API response's HTTP response code. - if (rootNode.get("status").asText().equals("200 OK")) { - // In the API response, the "result" field contains the - // list of needed results. - ArrayNode arrayNodeResults = (ArrayNode) rootNode.get("result"); - // Prompt the user to select a topic from the list of API - // results. - topicsId = getUserChoice(arrayNodeResults); - } - } finally { - instream.close(); - } - } - return topicsId; - } - - /** - * Output results from the Freebase topic search to the user, prompt the - * user to select a topic, and return the appropriate topic ID. - * - * @param freebaseResults ArrayNode This object contains the search results. - */ - private static String getUserChoice(ArrayNode freebaseResults) throws IOException { - - String freebaseId = ""; - - if (freebaseResults.size() < 1) { - return freebaseId; - } - - // Print a list of topics retrieved from Freebase. - for (int i = 0; i < freebaseResults.size(); i++) { - JsonNode node = freebaseResults.get(i); - System.out.print(" " + i + " = " + node.get("name").asText()); - if (node.get("notable") != null) { - System.out.print(" (" + node.get("notable").get("name").asText() + ")"); - } - System.out.println(""); - } - - BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in)); - String inputChoice; - - // Prompt the user to select a topic. - do { - System.out.print("Choose the number of the Freebase Node: "); - inputChoice = bReader.readLine(); - } while (!isValidIntegerSelection(inputChoice, freebaseResults.size())); - - // Return the topic ID needed for the API request that retrieves - // YouTube search results. - JsonNode node = freebaseResults.get(Integer.parseInt(inputChoice)); - freebaseId = node.get("mid").asText(); - return freebaseId; - } - - /** - * Confirm that the string represents a valid, positive integer, that is - * less than or equal to 999,999,999. (Since the API will not return a - * billion results for a query, any integer that the user enters will need - * to be less than that to be valid, anyway.) - * - * @param input The string to test. - * @param max The integer must be less then this maximum number. - */ - public static boolean isValidIntegerSelection(String input, int max) { - if (input.length() > 9) - return false; - - boolean validNumber = false; - // Only accept positive numbers with a maximum of nine digits. - Pattern intsOnly = Pattern.compile("^\\d{1,9}$"); - Matcher makeMatch = intsOnly.matcher(input); - - if (makeMatch.find()) { - int number = Integer.parseInt(makeMatch.group()); - if ((number >= 0) && (number < max)) { - validNumber = true; - } - } - return validNumber; - } - - /* - * Prints out all results in the Iterator. For each result, print the - * title, video ID, and thumbnail. - * - * @param iteratorSearchResults Iterator of SearchResults to print - * @param query Search query (String) - */ - private static void prettyPrint(Iterator iteratorSearchResults, String query, String topicsId) { - - System.out.println("\n============================================================="); - System.out.println(" First " + NUMBER_OF_VIDEOS_RETURNED + " videos for search on \"" + query + "\" with Topics id: " + topicsId + "."); - System.out.println("=============================================================\n"); - - if (!iteratorSearchResults.hasNext()) { - System.out.println(" There aren't any results for your query."); - } - - while (iteratorSearchResults.hasNext()) { - - SearchResult singleVideo = iteratorSearchResults.next(); - ResourceId rId = singleVideo.getId(); - - // Confirm that the result represents a video. Otherwise, the - // item will not contain a video ID. - if (rId.getKind().equals("youtube#video")) { - Thumbnail thumbnail = singleVideo.getSnippet().getThumbnails().getDefault(); - - System.out.println(" Video Id" + rId.getVideoId()); - System.out.println(" Title: " + singleVideo.getSnippet().getTitle()); - System.out.println(" Thumbnail: " + thumbnail.getUrl()); - System.out.println("\n-------------------------------------------------------------\n"); - } - } - } -} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/DeleteLiveChatMessage.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/DeleteLiveChatMessage.java new file mode 100644 index 00000000..51721623 --- /dev/null +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/DeleteLiveChatMessage.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.services.samples.youtube.cmdline.live; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.services.samples.youtube.cmdline.Auth; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTubeScopes; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.List; + +/** + * Delets a message from a live broadcast, using OAuth 2.0 to authorize API requests. + * + * @author Jim Rogers + */ +public class DeleteLiveChatMessage { + + /** + * Define a global instance of a Youtube object, which will be used + * to make YouTube Data API requests. + */ + private static YouTube youtube; + + /** + * Deletes a message from a live broadcast. + * + * @param args The message id to delete (required) followed by the videoId (optional). If the + * videoId is given, live chat messages will be retrieved from the chat associated with this + * video. If the videoId is not specified, the signed in user's current live broadcast will be + * used instead. + */ + public static void main(String[] args) { + // Get the message id to delete + if (args.length == 0) { + System.err.println("No message id specified"); + System.exit(1); + } + String messageId = args[0]; + + // This OAuth 2.0 access scope allows for write access to the authenticated user's account. + List scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_FORCE_SSL); + + try { + // Authorize the request. + Credential credential = Auth.authorize(scopes, "deletelivechatmessage"); + + // This object is used to make YouTube Data API requests. + youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, credential) + .setApplicationName("youtube-cmdline-deletechatmessages-sample").build(); + + // Delete the message from live chat + YouTube.LiveChatMessages.Delete liveChatDelete = + youtube.liveChatMessages().delete(messageId); + liveChatDelete.execute(); + System.out.println("Deleted message id " + messageId); + } catch (GoogleJsonResponseException e) { + System.err + .println("GoogleJsonResponseException code: " + e.getDetails().getCode() + " : " + + e.getDetails().getMessage()); + e.printStackTrace(); + } catch (IOException e) { + System.err.println("IOException: " + e.getMessage()); + e.printStackTrace(); + } catch (Throwable t) { + System.err.println("Throwable: " + t.getMessage()); + t.printStackTrace(); + } + } +} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java new file mode 100644 index 00000000..31602a05 --- /dev/null +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.services.samples.youtube.cmdline.live; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.services.samples.youtube.cmdline.Auth; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTubeScopes; +import com.google.api.services.youtube.model.LiveBroadcast; +import com.google.api.services.youtube.model.LiveBroadcastListResponse; +import com.google.api.services.youtube.model.Video; +import com.google.api.services.youtube.model.VideoListResponse; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.List; + +/** + * Gets a live chat id from a video id or current signed in user. + * + * The videoId is often included in the video's url, e.g.: + * https://www.youtube.com/watch?v=L5Xc93_ZL60 + * ^ videoId + * The video URL may be found in the browser address bar, or by right-clicking a video and selecting + * Copy video URL from the context menu. + * + * @author Jim Rogers + */ +public class GetLiveChatId { + + /** + * Define a global instance of a Youtube object, which will be used + * to make YouTube Data API requests. + */ + private static YouTube youtube; + + /** + * Poll live chat messages and SuperChat details from a live broadcast. + * + * @param args videoId (optional). If the videoId is given, live chat messages will be retrieved + * from the chat associated with this video. If the videoId is not specified, the signed in + * user's current live broadcast will be used instead. + */ + public static void main(String[] args) { + + // This OAuth 2.0 access scope allows for read-only access to the + // authenticated user's account, but not other types of account access. + List scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_READONLY); + + try { + // Authorize the request. + Credential credential = Auth.authorize(scopes, "getlivechatid"); + + // This object is used to make YouTube Data API requests. + youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, credential) + .setApplicationName("youtube-cmdline-getlivechatid-sample").build(); + + // Get the liveChatId + String liveChatId = args.length == 1 + ? getLiveChatId(youtube, args[0]) + : getLiveChatId(youtube); + if (liveChatId != null) { + System.out.println("Live chat id: " + liveChatId); + } else { + System.err.println("Unable to find a live chat id"); + System.exit(1); + } + } catch (GoogleJsonResponseException e) { + System.err + .println("GoogleJsonResponseException code: " + e.getDetails().getCode() + " : " + + e.getDetails().getMessage()); + e.printStackTrace(); + + } catch (IOException e) { + System.err.println("IOException: " + e.getMessage()); + e.printStackTrace(); + } catch (Throwable t) { + System.err.println("Throwable: " + t.getMessage()); + t.printStackTrace(); + } + } + + /** + * Retrieves the liveChatId from the authenticated user's live broadcast. + * + * @param youtube The object is used to make YouTube Data API requests. + * @return A liveChatId, or null if not found. + */ + static String getLiveChatId(YouTube youtube) throws IOException { + // Get signed in user's liveChatId + YouTube.LiveBroadcasts.List broadcastList = youtube + .liveBroadcasts() + .list("snippet") + .setFields("items/snippet/liveChatId") + .setBroadcastType("all") + .setBroadcastStatus("active"); + LiveBroadcastListResponse broadcastListResponse = broadcastList.execute(); + for (LiveBroadcast b : broadcastListResponse.getItems()) { + String liveChatId = b.getSnippet().getLiveChatId(); + if (liveChatId != null && !liveChatId.isEmpty()) { + return liveChatId; + } + } + + return null; + } + + /** + * Retrieves the liveChatId from the broadcast associated with a videoId. + * + * @param youtube The object is used to make YouTube Data API requests. + * @param videoId The videoId associated with the live broadcast. + * @return A liveChatId, or null if not found. + */ + static String getLiveChatId(YouTube youtube, String videoId) throws IOException { + // Get liveChatId from the video + YouTube.Videos.List videoList = youtube.videos() + .list("liveStreamingDetails") + .setFields("items/liveStreamingDetails/activeLiveChatId") + .setId(videoId); + VideoListResponse response = videoList.execute(); + for (Video v : response.getItems()) { + String liveChatId = v.getLiveStreamingDetails().getActiveLiveChatId(); + if (liveChatId != null && !liveChatId.isEmpty()) { + return liveChatId; + } + } + + return null; + } +} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/InsertLiveChatMessage.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/InsertLiveChatMessage.java new file mode 100644 index 00000000..09fe059c --- /dev/null +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/InsertLiveChatMessage.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.services.samples.youtube.cmdline.live; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.services.samples.youtube.cmdline.Auth; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTubeScopes; +import com.google.api.services.youtube.model.LiveChatMessage; +import com.google.api.services.youtube.model.LiveChatMessageSnippet; +import com.google.api.services.youtube.model.LiveChatTextMessageDetails; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.List; + +/** + * Inserts a message into a live broadcast of the current user or a video specified by id. + * + * The videoId is often included in the video's url, e.g.: + * https://www.youtube.com/watch?v=L5Xc93_ZL60 + * ^ videoId + * The video URL may be found in the browser address bar, or by right-clicking a video and selecting + * Copy video URL from the context menu. + * + * @author Jim Rogers + */ +public class InsertLiveChatMessage { + + /** + * Define a global instance of a Youtube object, which will be used + * to make YouTube Data API requests. + */ + private static YouTube youtube; + + /** + * Inserts a message into a live broadcast. + * + * @param args The message to insert (required) followed by the videoId (optional). + * If the videoId is given, live chat messages will be retrieved from the chat associated with + * this video. If the videoId is not specified, the signed in user's current live broadcast will + * be used instead. + */ + public static void main(String[] args) { + // Get the chat message to insert + if (args.length == 0) { + System.err.println("No message specified"); + System.exit(1); + } + String message = args[0]; + + // This OAuth 2.0 access scope allows for write access to the authenticated user's account. + List scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_FORCE_SSL); + + try { + // Authorize the request. + Credential credential = Auth.authorize(scopes, "insertlivechatmessage"); + + // This object is used to make YouTube Data API requests. + youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, credential) + .setApplicationName("youtube-cmdline-insertchatmessage-sample").build(); + + // Get the liveChatId + String liveChatId = args.length == 2 + ? GetLiveChatId.getLiveChatId(youtube, args[1]) + : GetLiveChatId.getLiveChatId(youtube); + if (liveChatId != null) { + System.out.println("Live chat id: " + liveChatId); + } else { + System.err.println("Unable to find a live chat id"); + System.exit(1); + } + + // Insert the message into live chat + LiveChatMessage liveChatMessage = new LiveChatMessage(); + LiveChatMessageSnippet snippet = new LiveChatMessageSnippet(); + snippet.setType("textMessageEvent"); + snippet.setLiveChatId(liveChatId); + LiveChatTextMessageDetails details = new LiveChatTextMessageDetails(); + details.setMessageText(message); + snippet.setTextMessageDetails(details); + liveChatMessage.setSnippet(snippet); + YouTube.LiveChatMessages.Insert liveChatInsert = + youtube.liveChatMessages().insert("snippet", liveChatMessage); + LiveChatMessage response = liveChatInsert.execute(); + System.out.println("Inserted message id " + response.getId()); + } catch (GoogleJsonResponseException e) { + System.err + .println("GoogleJsonResponseException code: " + e.getDetails().getCode() + " : " + + e.getDetails().getMessage()); + e.printStackTrace(); + } catch (IOException e) { + System.err.println("IOException: " + e.getMessage()); + e.printStackTrace(); + } catch (Throwable t) { + System.err.println("Throwable: " + t.getMessage()); + t.printStackTrace(); + } + } +} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListBroadcasts.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListBroadcasts.java index 0fafb563..6a421267 100644 --- a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListBroadcasts.java +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListBroadcasts.java @@ -63,8 +63,8 @@ public static void main(String[] args) { youtube.liveBroadcasts().list("id,snippet"); // Indicate that the API response should not filter broadcasts - // based on their status. - liveBroadcastRequest.setBroadcastStatus("all"); + // based on their type or status. + liveBroadcastRequest.setBroadcastType("all").setBroadcastStatus("all"); // Execute the API request and return the list of broadcasts. LiveBroadcastListResponse returnedListResponse = liveBroadcastRequest.execute(); diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListLiveChatMessages.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListLiveChatMessages.java new file mode 100644 index 00000000..463326ed --- /dev/null +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/ListLiveChatMessages.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.services.samples.youtube.cmdline.live; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.services.samples.youtube.cmdline.Auth; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTubeScopes; +import com.google.api.services.youtube.model.LiveChatMessage; +import com.google.api.services.youtube.model.LiveChatMessageAuthorDetails; +import com.google.api.services.youtube.model.LiveChatMessageListResponse; +import com.google.api.services.youtube.model.LiveChatMessageSnippet; +import com.google.api.services.youtube.model.LiveChatSuperChatDetails; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Lists live chat messages and SuperChat details from a live broadcast. + * + * The videoId is often included in the video's url, e.g.: + * https://www.youtube.com/watch?v=L5Xc93_ZL60 + * ^ videoId + * The video URL may be found in the browser address bar, or by right-clicking a video and selecting + * Copy video URL from the context menu. + * + * @author Jim Rogers + */ +public class ListLiveChatMessages { + + /** + * Common fields to retrieve for chat messages + */ + private static final String LIVE_CHAT_FIELDS = + "items(authorDetails(channelId,displayName,isChatModerator,isChatOwner,isChatSponsor," + + "profileImageUrl),snippet(displayMessage,superChatDetails,publishedAt))," + + "nextPageToken,pollingIntervalMillis"; + + /** + * Define a global instance of a Youtube object, which will be used + * to make YouTube Data API requests. + */ + private static YouTube youtube; + + /** + * Lists live chat messages and SuperChat details from a live broadcast. + * + * @param args videoId (optional). If the videoId is given, live chat messages will be retrieved + * from the chat associated with this video. If the videoId is not specified, the signed in + * user's current live broadcast will be used instead. + */ + public static void main(String[] args) { + + // This OAuth 2.0 access scope allows for read-only access to the + // authenticated user's account, but not other types of account access. + List scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_READONLY); + + try { + // Authorize the request. + Credential credential = Auth.authorize(scopes, "listlivechatmessages"); + + // This object is used to make YouTube Data API requests. + youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, credential) + .setApplicationName("youtube-cmdline-listchatmessages-sample").build(); + + // Get the liveChatId + String liveChatId = args.length == 1 + ? GetLiveChatId.getLiveChatId(youtube, args[0]) + : GetLiveChatId.getLiveChatId(youtube); + if (liveChatId != null) { + System.out.println("Live chat id: " + liveChatId); + } else { + System.err.println("Unable to find a live chat id"); + System.exit(1); + } + + // Get live chat messages + listChatMessages(liveChatId, null, 0); + } catch (GoogleJsonResponseException e) { + System.err + .println("GoogleJsonResponseException code: " + e.getDetails().getCode() + " : " + + e.getDetails().getMessage()); + e.printStackTrace(); + + } catch (IOException e) { + System.err.println("IOException: " + e.getMessage()); + e.printStackTrace(); + } catch (Throwable t) { + System.err.println("Throwable: " + t.getMessage()); + t.printStackTrace(); + } + } + + /** + * Lists live chat messages, polling at the server supplied interval. Owners and moderators of a + * live chat will poll at a faster rate. + * + * @param liveChatId The live chat id to list messages from. + * @param nextPageToken The page token from the previous request, if any. + * @param delayMs The delay in milliseconds before making the request. + */ + private static void listChatMessages( + final String liveChatId, + final String nextPageToken, + long delayMs) { + System.out.println( + String.format("Getting chat messages in %1$.3f seconds...", delayMs * 0.001)); + Timer pollTimer = new Timer(); + pollTimer.schedule( + new TimerTask() { + @Override + public void run() { + try { + // Get chat messages from YouTube + LiveChatMessageListResponse response = youtube + .liveChatMessages() + .list(liveChatId, "snippet, authorDetails") + .setPageToken(nextPageToken) + .setFields(LIVE_CHAT_FIELDS) + .execute(); + + // Display messages and super chat details + List messages = response.getItems(); + for (int i = 0; i < messages.size(); i++) { + LiveChatMessage message = messages.get(i); + LiveChatMessageSnippet snippet = message.getSnippet(); + System.out.println(buildOutput( + snippet.getDisplayMessage(), + message.getAuthorDetails(), + snippet.getSuperChatDetails())); + } + + // Request the next page of messages + listChatMessages( + liveChatId, + response.getNextPageToken(), + response.getPollingIntervalMillis()); + } catch (Throwable t) { + System.err.println("Throwable: " + t.getMessage()); + t.printStackTrace(); + } + } + }, delayMs); + } + + /** + * Formats a chat message for console output. + * + * @param message The display message to output. + * @param author The author of the message. + * @param superChatDetails SuperChat details associated with the message. + * @return A formatted string for console output. + */ + private static String buildOutput( + String message, + LiveChatMessageAuthorDetails author, + LiveChatSuperChatDetails superChatDetails) { + StringBuilder output = new StringBuilder(); + if (superChatDetails != null) { + output.append(superChatDetails.getAmountDisplayString()); + output.append("SUPERCHAT RECEIVED FROM "); + } + output.append(author.getDisplayName()); + List roles = new ArrayList(); + if (author.getIsChatOwner()) { + roles.add("OWNER"); + } + if (author.getIsChatModerator()) { + roles.add("MODERATOR"); + } + if (author.getIsChatSponsor()) { + roles.add("SPONSOR"); + } + if (roles.size() > 0) { + output.append(" ("); + String delim = ""; + for (String role : roles) { + output.append(delim).append(role); + delim = ", "; + } + output.append(")"); + } + if (message != null && !message.isEmpty()) { + output.append(": "); + output.append(message); + } + return output.toString(); + } +} diff --git a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/RetrieveReports.java b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/RetrieveReports.java index 0757a409..211bb0d5 100644 --- a/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/RetrieveReports.java +++ b/java/src/main/java/com/google/api/services/samples/youtube/cmdline/reporting/RetrieveReports.java @@ -16,6 +16,7 @@ import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.GenericUrl; import com.google.api.services.samples.youtube.cmdline.Auth; import com.google.api.services.youtubereporting.YouTubeReporting; import com.google.api.services.youtubereporting.YouTubeReporting.Media.Download; diff --git a/javascript/README.md b/javascript/README.md new file mode 100644 index 00000000..a51d446a --- /dev/null +++ b/javascript/README.md @@ -0,0 +1,75 @@ +## Samples in this directory: + +### [Authorize a request](/javascript/auth.js) + +Description: The auth.js script demonstrates how to use the Google APIs Client Library for JavaScript +to provide API access and authorize user requests. All of the subsequent samples on this page use this script to +authorize their requests.

+For requests that do not require authentication, you could also use the +key query parameter to specify an API key rather than using OAuth 2.0.

+Note: You need to update the client ID in the auth.js file. You can obtain your own +client ID by registering your application in the +Google Developers Console. + +### [Do resumable uploads with CORS](/javascript/cors_upload.js) + +Method: youtube.videos.insert +Description: This code sample demonstrates how to execute a resumable upload using XHR/CORS. + +### [Create a playlist](/javascript/playlist_updates.js) + +Method: youtube.playlists.insert
+Description: This JavaScript code creates a private playlist and adds videos to that playlist. (You could, of course, modify the code so that it creates a publicly visible playlist or so that it checks a form value to determine whether the playlist is +public or private.) Note that you need to update the client ID in the auth.js file to run this code.

The HTML page uses JQuery, along with the auth.js and playlist_updates.js JavaScript files, to display a simple form for adding videos to the playlist. + +### [Retrieve my uploads](/javascript/my_uploads.js) + +Method: youtube.playlistItems.list
+Description: The JavaScript sample code performs the following functions:
+
    +
  1. It retrieves the playlist ID for videos uploaded to the user's channel using the API's channels.list method. This API call also sets the mine parameter to true to retrieve channel information for the authorizing user.
  2. +
  3. It passes that ID to the playlistItems.list method to retrieve the videos in that list.
  4. +
  5. It displays the list of videos.
  6. +
  7. It constructs next and previous page buttons and sets their visibility based on the information in the API response.
  8. +
+ +The HTML page uses JQuery, the auth.js and my_uploads.js JavaScript files, and a CSS file to display the list of uploaded videos. + +### [Search by keyword](/javascript/search.js) + +Method: youtube.search.list
+Description: This code sample calls the API's search.list method to retrieve search results associated +with a particular keyword. The HTML page uses JQuery, along with the auth.js and search.js JavaScript files, to show a simple search form and display the list of search results. + +### [Upload a video](/javascript/upload_video.js) + +Method: youtube.videos.insert
+Description: This JavaScript sample performs the following functions:
+
    +
  1. It retrieves the channel name and thumbnail of the authenticated user's channel using the API's channels.list method.
  2. +
  3. It handles the video upload to YouTube using the resumable upload protocol.
  4. +
  5. It polls for the uploaded video's upload and processing status using the API's videos.list method by setting the part parameter value to status.
  6. +
+ +The HTML page uses JQuery, the cors_upload.js and upload_video.js JavaScript files, and the +upload_video.css file to upload a video file to YouTube.

Note that if you use this code in your own application, you must replace the value of the data-clientid attribute in the code for the Sign-In Button +with your project's client ID. The only valid JavaScript origin for the client ID in the sample code is +http://localhost. This means that you could test the sample locally, but it would not work in your +production application. + +### [Calling the Analytics API](/javascript/analytics_codelab.js) + +Method: youtubeAnalytics.reports.query
+Description: This sample uses the YouTube Data and YouTube Analytics APIs to retrieve YouTube channel metrics. +The samples use the Google APIs JavaScript client library +to demonstrate API functionality. The Building a Sample Application +document walks you through the steps of building this application and discusses different portions of this code in more +detail.

+ +In addition to the JavaScript file described above, the HTML page uses jQuery, which +provides helper methods to simplify HTML document traversing, event handling, animating and Ajax interactions. It also +uses the Google API loader (www.google.com/jsapi), +which lets you easily import one or more Google APIs. This example uses the API loader to load the Google Visualization API, +which is used to chart the retrieved Analytics data. Finally, the +Google APIs Client Library for JavaScript +helps you to implement OAuth 2.0 authentication and to call the YouTube Analytics API. diff --git a/javascript/nodejs-quickstart.js b/javascript/nodejs-quickstart.js new file mode 100644 index 00000000..68d0591f --- /dev/null +++ b/javascript/nodejs-quickstart.js @@ -0,0 +1,125 @@ +var fs = require('fs'); +var readline = require('readline'); +var {google} = require('googleapis'); +var OAuth2 = google.auth.OAuth2; + +// If modifying these scopes, delete your previously saved credentials +// at ~/.credentials/youtube-nodejs-quickstart.json +var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; +var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH || + process.env.USERPROFILE) + '/.credentials/'; +var TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json'; + +// Load client secrets from a local file. +fs.readFile('client_secret.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + // Authorize a client with the loaded credentials, then call the YouTube API. + authorize(JSON.parse(content), getChannel); +}); + +/** + * Create an OAuth2 client with the given credentials, and then execute the + * given callback function. + * + * @param {Object} credentials The authorization client credentials. + * @param {function} callback The callback to call with the authorized client. + */ +function authorize(credentials, callback) { + var clientSecret = credentials.installed.client_secret; + var clientId = credentials.installed.client_id; + var redirectUrl = credentials.installed.redirect_uris[0]; + var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl); + + // Check if we have previously stored a token. + fs.readFile(TOKEN_PATH, function(err, token) { + if (err) { + getNewToken(oauth2Client, callback); + } else { + oauth2Client.credentials = JSON.parse(token); + callback(oauth2Client); + } + }); +} + +/** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * + * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback to call with the authorized + * client. + */ +function getNewToken(oauth2Client, callback) { + var authUrl = oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES + }); + console.log('Authorize this app by visiting this url: ', authUrl); + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + rl.question('Enter the code from that page here: ', function(code) { + rl.close(); + oauth2Client.getToken(code, function(err, token) { + if (err) { + console.log('Error while trying to retrieve access token', err); + return; + } + oauth2Client.credentials = token; + storeToken(token); + callback(oauth2Client); + }); + }); +} + +/** + * Store token to disk be used in later program executions. + * + * @param {Object} token The token to store to disk. + */ +function storeToken(token) { + try { + fs.mkdirSync(TOKEN_DIR); + } catch (err) { + if (err.code != 'EEXIST') { + throw err; + } + } + fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { + if (err) throw err; + console.log('Token stored to ' + TOKEN_PATH); + }); +} + +/** + * Lists the names and IDs of up to 10 files. + * + * @param {google.auth.OAuth2} auth An authorized OAuth2 client. + */ +function getChannel(auth) { + var service = google.youtube('v3'); + service.channels.list({ + auth: auth, + part: 'snippet,contentDetails,statistics', + forUsername: 'GoogleDevelopers' + }, function(err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + var channels = response.data.items; + if (channels.length == 0) { + console.log('No channel found.'); + } else { + console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + + 'it has %s views.', + channels[0].id, + channels[0].snippet.title, + channels[0].statistics.viewCount); + } + }); +} diff --git a/javascript/quickstart.html b/javascript/quickstart.html new file mode 100644 index 00000000..a8f264d5 --- /dev/null +++ b/javascript/quickstart.html @@ -0,0 +1,120 @@ + + + + YouTube Data API Quickstart + + + +

YouTube Data API Quickstart

+ + + + + +

+
+    
+
+    
+  
+
diff --git a/javascript/yt_analytics_v2.html b/javascript/yt_analytics_v2.html
new file mode 100644
index 00000000..7ab4cf41
--- /dev/null
+++ b/javascript/yt_analytics_v2.html
@@ -0,0 +1,35 @@
+
+
+
+
diff --git a/php/README.md b/php/README.md
new file mode 100644
index 00000000..8a4b63be
--- /dev/null
+++ b/php/README.md
@@ -0,0 +1,227 @@
+## Samples in this directory:
+
+### [Add a channel section](/php/add_channel_section.php)
+
+Method: youtube.channelSections.insert
+Description: This sample calls the API's channelSections.insert method to create channel sections. +The code accepts a number of command line arguments that let you specify the section's type, display style, title, position, +and content.

+This sample also updates the channel's +brandingSettings.channel.showBrowseView +property so that the channel displays content in a browse view (rather than a feed view). A channel's sections are only +visible if the channel displays content in a browse view.

More information on channel sections is available in the +YouTube Help Center. + +### [Add a channel subscription](/php/add_subscription.php) + +Method: youtube.subscriptions.insert
+Description: This sample calls the API's subscriptions.insert method to add a subscription to a specified +channel. + +### [Create a playlist](/php/playlist_updates.php) + +Method: youtube.playlists.insert
+Description: This sample calls the API's playlists.insert method to create a private playlist owned by the +channel authorizing the request. + +### [Create and manage comments](/php/comment_handling.php) + +Method: youtube.commentThreads.list, youtube.comments.insert, youtube.comments.list, youtube.comments.update, +youtube.comments.setModerationStatus, youtube.comments.markAsSpam, youtube.comments.delete
+Description: The following code sample demonstrates how to use the following API methods to create and manage comments:
+
    +
  • It calls the commentThreads.list method with the videoId parameter set to retrieve comments +for a video.
  • +
  • It calls the comments.insert method with the parentId parameter set to reply to an existing +comment.
  • +
  • It calls the comments.list method with the parentId parameter to retrieve the comments in the +thread.
  • +
  • It calls the comments.update method with comment in the request body to update a comment.
  • +
  • It calls the comments.setModerationStatus method to set the moderation status of the comment, the +comments.markAsSpam method to mark the comment as spam, and the comments.delete method to +delete the comment, using the id parameter to identify the comment.
  • +
+ +### [Create and manage comment threads](/php/comment_threads.php) + +Method: youtube.commentThreads.insert, youtube.commentThreads.list, youtube.commentThreads.update
+Description: This sample demonstrates how to use the following API methods to create and manage top-level comments:
+
    +
  • It calls the commentThreads.insert method once with the channelId parameter to create a +channel comment and once with the videoId parameter to create a video comment.
  • +
  • It calls the commentThreads.list method once with the channelId parameter to retrieve +channel comments and once with the videoId parameter to retrieve video comments.
  • +
  • It calls the commentThreads.update method once to update a video comment and then again to update a +channel comment. In each case, the request body contains the comment resource being updated.
  • +
+ +### [Create and manage YouTube video caption tracks](/php/captions.php) + +Method: youtube.captions.insert, youtube.captions.list, youtube.captions.update, youtube.captions.download, +youtube.captions.delete
+Description: This sample demonstrates how to use the following API methods to create and manage YouTube video caption +tracks:
+
    +
  • It calls the captions.insert method with the isDraft parameter set to true +to upload a caption track in draft status.
  • +
  • It calls the captions.list method with the videoId parameter to retrieve video caption +tracks.
  • +
  • It calls the captions.update method with the caption in the request body to update a caption track.
  • +
  • It calls the captions.download method to download the caption track.
  • +
  • It calls the captions.delete method to delete the caption track, using the id parameter to +identify the caption track.
  • +
+ +### [Retrieve my uploads](/php/my_uploads.php) + +Method: youtube.playlistItems.list
+Description: This sample calls the API's playlistItems.list method to retrieve a list of videos uploaded +to the channel associated with the request. The code also calls the channels.list method with the +mine parameter set to true to retrieve the playlist ID that identifies the channel's uploaded +videos. + +### [Search by keyword](/php/search.php) + +Method: youtube.search.list
+Description: This sample calls the API's search.list method to retrieve search results associated with +a particular keyword. + +### [Search by location](/php/geolocation_search.php) + +Method: youtube.search.list, youtube.videos.list
+Description: This sample calls the API's search.list method with the type, q, +location and locationRadius parameters to retrieve search results matching the provided +keyword within the radius centered at a particular location. Using the video IDs from the search result, the sample +calls the API's videos.list method to retrieve location details of each video. + +### [Set and retrieve localized channel metadata](/php/channel_localizations.php) + +Method: youtube.channels.update, youtube.channels.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel:
+
    +
  • It calls the channels.update method to update the default language of a channel's metadata and to add a +localized version of this metadata in a selected language. Note that to set the default language for a channel resource, +you actually need to update the brandingSettings.channel.defaultLanguage property.
  • +
  • It calls the channels.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the channels.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that channel.
  • +
+ +### [Set and retrieve localized channel section metadata](/php/channel_section_localizations.php) + +Method: youtube.channelSections.update, youtube.channelSections.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel section:
+
    +
  • It calls the channelSections.update method to update the default language of a channel section's +metadata and to add a localized version of this metadata in a selected language.
  • +
  • It calls the channelSections.list method with the hl parameter set to a specific language +to retrieve localized metadata in that language.
  • +
  • It calls the channelSections.list method and includes localizations in the +part parameter value to retrieve all of the localized metadata for that channel section.
  • +
+ +### [Set and retrieve localized playlist metadata](/php/playlist_localizations.php) + +Method: youtube.playlists.update, youtube.playlists.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +playlist:
+
    +
  • It calls the playlists.update method to update the default language of a playlist's metadata and to add +a localized version of this metadata in a selected language.
  • +
  • It calls the playlists.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the playlists.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that playlist.
  • +
+ +### [Set and retrieve localized video metadata](/php/video_localizations.php) + +Method: youtube.videos.update, youtube.videos.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata +for a video:
+
    +
  • It calls the videos.update method to update the default language of a video's metadata and to add +a localized version of this metadata in a selected language.
  • +
  • It calls the videos.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the videos.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that video.
  • +
+ +### [Shuffle existing channel sections](/php/shuffle_channel_sections.php) + +Method: youtube.channelSections.list,youtube.channelSections.update
+Description: This sample calls the API's channelSections.list method to get the list of current channel +sections, shuffles them, and then calls channelSections.update to change the position of each.

+More information on channel sections is available in the +YouTube Help Center. + +### [Update a video](/php/update_video.php) + +Method: youtube.videos.update
+Description: This code sample demonstrates how to add tags into an existing video.

The following code +sample calls the API's youtube.videos.list method with id parameter set to videoId +to get the video object. Using this video object, the sample gets the list of tags and appends new tags at the +end of this list. Finally, the code calls youtube.videos.update method with updated video object +to persist these changes on YouTube. + +### [Upload a banner image and set as channel's banner](/php/upload_banner.php) + +Method: youtube.channelBanners.insert, youtube.channels.update
+Description: This sample calls the API's channelBanners.insert method to upload an image. With the +returned URL, the sample calls channels.update method to update the channel's banner to this image. + +### [Upload a custom video thumbnail image](/php/upload_thumbnail.php) + +Method: youtube.thumbnails.set
+Description: This sample demonstrates how to upload a custom video thumbnail to YouTube and set it for a video. +It calls the API's youtube.thumbnails.set method with videoId parameter set to a video +ID to use a custom image as a thumbnail to the video. For the image upload, the program utilizes the +Google_MediaFileUpload class with the resumable parameter set to +true to upload the image piece-by-piece, allowing for subsequent retries to resume uploading from +a point close to where the previous retry failed, a feature useful for programs that need to upload large files. + +### [Upload a video](/php/resumable_upload.php) + +Method: youtube.videos.insert
+Description: The following code sample calls the API's videos.insert method to add a video to user's +channel. The code also utilizes Google_MediaFileUpload class with the resumable upload +parameter set to true to be able to to upload the video in chunks. + +### [Create a broadcast and stream](/php/create_broadcast.php) + +Method: youtube.liveBroadcasts.bind,youtube.liveBroadcasts.insert,youtube.liveStreams.insert
+Description: This sample calls the API's liveBroadcasts.insert and liveStreams.insert +methods to create a broadcast and a stream. Then, it calls the liveBroadcasts.bind method to bind +the stream to the broadcast. + +### [Retrieve a channel's broadcasts](/php/list_broadcasts.php) + +Method: youtube.liveBroadcasts.list
+Description: This sample calls the API's liveBroadcasts.list method to retrieve a list of broadcasts for +the channel associated with the request. By default, the request retrieves all broadcasts for the channel, but you can +also specify a value for the --broadcast-status option to only retrieve broadcasts with a particular status. + +### [Retrieve a channel's live video streams](/php/list_streams.php) + +Method: youtube.liveStreams.list
+Description: This sample calls the API's liveStreams.list method to retrieve a list of video stream settings +that a channel can use to broadcast live events on YouTube. + +### [Create a reporting job](/php/create_reporting_job.php) + +Method: youtubeReporting.reportTypes.list, youtubeReporting.jobs.create
+Description: This sample demonstrates how to create a reporting job. It calls the reportTypes.list method +to retrieve a list of available report types. It then calls the jobs.create method to create a new reporting +job. + +### [Retrieve reports](/php/retrieve_reports.php) + +Method: youtubeReporting.jobs.list, youtubeReporting.reports.list
+Description: This sample demonstrates how to retrieve reports created by a specific job. It calls the +jobs.list method to retrieve reporting jobs. It then calls the reports.list method with the +jobId parameter set to a specific job id to retrieve reports created by that job. Finally, the sample +prints out the download URL for each report. diff --git a/php/quickstart.php b/php/quickstart.php new file mode 100644 index 00000000..0af550ff --- /dev/null +++ b/php/quickstart.php @@ -0,0 +1,112 @@ +setAuthConfigFile('client_secret.json'); + // Set to valid redirect URI for your project. + $client->setRedirectUri('http://localhost'); + + $client->addScope(Google_Service_YouTube::YOUTUBE_READONLY); + $client->setAccessType('offline'); + + // Load previously authorized credentials from a file. + $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH); + if (file_exists($credentialsPath)) { + $accessToken = file_get_contents($credentialsPath); + } else { + // Request authorization from the user. + $authUrl = $client->createAuthUrl(); + printf("Open the following link in your browser:\n%s\n", $authUrl); + print 'Enter verification code: '; + $authCode = trim(fgets(STDIN)); + + // Exchange authorization code for an access token. + $accessToken = $client->authenticate($authCode); + + // Store the credentials to disk. + if(!file_exists(dirname($credentialsPath))) { + mkdir(dirname($credentialsPath), 0700, true); + } + file_put_contents($credentialsPath, $accessToken); + printf("Credentials saved to %s\n", $credentialsPath); + } + $client->setAccessToken($accessToken); + + // Refresh the token if it's expired. + if ($client->isAccessTokenExpired()) { + $client->refreshToken($client->getRefreshToken()); + file_put_contents($credentialsPath, $client->getAccessToken()); + } + return $client; +} + +/** + * Expands the home directory alias '~' to the full path. + * @param string $path the path to expand. + * @return string the expanded path. + */ +function expandHomeDirectory($path) { + $homeDirectory = getenv('HOME'); + if (empty($homeDirectory)) { + $homeDirectory = getenv("HOMEDRIVE") . getenv("HOMEPATH"); + } + return str_replace('~', realpath($homeDirectory), $path); +} + +// Define an object that will be used to make all API requests. +$client = getClient(); +$service = 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(); + header('Location: ' . $redirect); +} + +if (isset($_SESSION['token'])) { + $client->setAccessToken($_SESSION['token']); +} + +if (!$client->getAccessToken()) { + print("no access token, whaawhaaa"); + exit; +} + +// Call channels.list to retrieve information + +function channelsListByUsername($service, $part, $params) { + $params = array_filter($params); + $response = $service->channels->listChannels( + $part, + $params + ); + + $description = sprintf( + 'This channel\'s ID is %s. Its title is %s, and it has %s views.', + $response['items'][0]['id'], + $response['items'][0]['snippet']['title'], + $response['items'][0]['statistics']['viewCount']); + print $description . "\n"; +} + +channelsListByUsername($service, 'snippet,contentDetails,statistics', array('forUsername' => 'GoogleDevelopers')); +?> diff --git a/php/retrieve_reports.php b/php/retrieve_reports.php index f5b71fb2..3bd986f8 100644 --- a/php/retrieve_reports.php +++ b/php/retrieve_reports.php @@ -1,12 +1,15 @@ * For more information about using OAuth 2.0 to access Google APIs, please see: * * Please ensure that you have enabled the YouTube Data API for your project. */ -$OAUTH2_CLIENT_ID = 'REPLACE_ME'; -$OAUTH2_CLIENT_SECRET = 'REPLACE_ME'; - -$client = new Google_Client(); -$client->setClientId($OAUTH2_CLIENT_ID); -$client->setClientSecret($OAUTH2_CLIENT_SECRET); - -/* - * This OAuth 2.0 access scope allows for full read/write access to the - * authenticated user's account. - */ -$client->setScopes('https://www.googleapis.com/auth/yt-analytics-monetary.readonly'); -$redirect = filter_var('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'], - FILTER_SANITIZE_URL); -$client->setRedirectUri($redirect); - -// YouTube Reporting object used to make YouTube Reporting API requests. -$youtubeReporting = new Google_Service_YoutubeReporting($client); +function getClient() { + $client = new Google_Client(); + $client->setAuthConfigFile('client_secrets_php.json'); + $client->addScope( + 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly'); + $client->setRedirectUri('urn:ietf:wg:oauth:2.0:oob'); + $client->setAccessType('offline'); + + // Load previously authorized credentials from a file. + $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH); + if (file_exists($credentialsPath)) { + $accessToken = json_decode(file_get_contents($credentialsPath), true); + } else { + // Request authorization from the user. + $authUrl = $client->createAuthUrl(); + printf('Open the following link in your browser:\n%s\n', $authUrl); + print 'Enter verification code: '; + $authCode = trim(fgets(STDIN)); + + // Exchange authorization code for an access token. + $accessToken = $client->authenticate($authCode); + $refreshToken = $client->getRefreshToken(); + + // Store the credentials to disk. + if(!file_exists(dirname($credentialsPath))) { + mkdir(dirname($credentialsPath), 0700, true); + } + file_put_contents($credentialsPath, json_encode($accessToken)); + printf('Credentials saved to %s\n', $credentialsPath); -// Check if an auth token exists for the required scopes -$tokenSessionKey = 'token-' . $client->prepareScopes(); -if (isset($_GET['code'])) { - if (strval($_SESSION['state']) !== strval($_GET['state'])) { - die('The session state did not match.'); + //fclose($fp); } + $client->setAccessToken($accessToken); - $client->authenticate($_GET['code']); - $_SESSION[$tokenSessionKey] = $client->getAccessToken(); - header('Location: ' . $redirect); -} + // Refresh the token if it's expired. + if ($client->isAccessTokenExpired()) { + $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); + file_put_contents($credentialsPath, json_encode($client->getAccessToken())); + } -if (isset($_SESSION[$tokenSessionKey])) { - $client->setAccessToken($_SESSION[$tokenSessionKey]); + return $client; } -// Check to ensure that the access token was successfully acquired. -if ($client->getAccessToken()) { - $htmlBody = ''; - try { - if (empty(listReportingJobs($youtubeReporting, $htmlBody))) { - $htmlBody .= sprintf('

No jobs found.

'); - } else if ($_GET['reportUrl']){ - downloadReport($youtubeReporting, $_GET['reportUrl'], $htmlBody); - } else if ($_GET['jobId']){ - retrieveReports($youtubeReporting, $_GET['jobId'], $htmlBody); - } - } catch (Google_Service_Exception $e) { - $htmlBody .= sprintf('

A service error occurred: %s

', - htmlspecialchars($e->getMessage())); - } catch (Google_Exception $e) { - $htmlBody .= sprintf('

An client error occurred: %s

', - htmlspecialchars($e->getMessage())); +/** + * Expands the home directory alias '~' to the full path. + * @param string $path the path to expand. + * @return string the expanded path. + */ +function expandHomeDirectory($path) { + $homeDirectory = getenv('HOME'); + if (empty($homeDirectory)) { + $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH'); } - $_SESSION[$tokenSessionKey] = $client->getAccessToken(); -} elseif ($OAUTH2_CLIENT_ID == 'REPLACE_ME') { - $htmlBody = <<Client Credentials Required -

- You need to set \$OAUTH2_CLIENT_ID and - \$OAUTH2_CLIENT_ID before proceeding. -

-END; -} 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 = <<Authorization Required -

You need to authorize access before proceeding.

-END; + return str_replace('~', realpath($homeDirectory), $path); } - /** * Returns a list of reporting jobs. (jobs.listJobs) * * @param Google_Service_YouTubereporting $youtubeReporting YouTube Reporting service object. - * @param $htmlBody - html body. + * @param string $onBehalfOfContentOwner A content owner ID. */ -function listReportingJobs(Google_Service_YouTubeReporting $youtubeReporting, &$htmlBody) { - // Call the YouTube Reporting API's jobs.list method to retrieve reporting jobs. - $reportingJobs = $youtubeReporting->jobs->listJobs(); - - $htmlBody .= "

Reporting Jobs

    "; +function listReportingJobs(Google_Service_YouTubeReporting $youtubeReporting, + $onBehalfOfContentOwner = '', $includeSystemManaged = False) { + $reportingJobs = $youtubeReporting->jobs->listJobs( + array('onBehalfOfContentOwner' => $onBehalfOfContentOwner, + 'includeSystemManaged' => $includeSystemManaged)); + print ('REPORTING JOBS' . PHP_EOL . '**************' . PHP_EOL); foreach ($reportingJobs as $job) { - $htmlBody .= sprintf('
  • id: "%s", name: "%s" report type: "%s"
  • ', $job['id'], - $job['name'], $job['reportTypeId']); + print($job['reportTypeId'] . ':' . $job['id'] . PHP_EOL); } - $htmlBody .= '
'; - - return $reportingJobs; + print(PHP_EOL); } - /** * Lists reports created by a specific job. (reports.listJobsReports) * * @param Google_Service_YouTubereporting $youtubeReporting YouTube Reporting service object. * @param string $jobId The ID of the job. - * @param $htmlBody - html body. + * @param string $onBehalfOfContentOwner A content owner ID. */ -function retrieveReports(Google_Service_YouTubeReporting $youtubeReporting, $jobId, &$htmlBody) { - // Call the YouTube Reporting API's reports.list method to retrieve reports created by a job. - $reports = $youtubeReporting->jobs_reports->listJobsReports($jobId); - - if (empty($reports)) { - $htmlBody .= sprintf('

No reports found.

'); - } else { - $htmlBody .= sprintf('

Reports for the job "%s"

    ', $jobId); - foreach ($reports as $report) { - $htmlBody .= sprintf('
  • From "%s" to "%s" downloadable at "%s"
  • ', - $report['startTime'], $report['endTime'], $report['downloadUrl']); - $htmlBody .= '
'; - } +function listReportsForJob(Google_Service_YouTubeReporting $youtubeReporting, + $jobId, $onBehalfOfContentOwner = '') { + $reports = $youtubeReporting->jobs_reports->listJobsReports($jobId, + array('onBehalfOfContentOwner' => $onBehalfOfContentOwner)); + print ('DOWNLOADABLE REPORTS' . PHP_EOL . '********************' . PHP_EOL); + foreach ($reports['reports'] as $report) { + print('Created: ' . date('d M Y', strtotime($report['createTime'])) . + ' (' . date('d M Y', strtotime($report['startTime'])) . + ' to ' . date('d M Y', strtotime($report['endTime'])) . ')' . + PHP_EOL . ' ' . $report['downloadUrl'] . PHP_EOL . PHP_EOL); } } - /** * Download the report specified by the URL. (media.download) * * @param Google_Service_YouTubereporting $youtubeReporting YouTube Reporting service object. * @param string $reportUrl The URL of the report to be downloaded. + * @param string $outputFile The file to write the report to locally. * @param $htmlBody - html body. */ -function downloadReport(Google_Service_YouTubeReporting $youtubeReporting, $reportUrl, &$htmlBody) { +function downloadReport(Google_Service_YouTubeReporting $youtubeReporting, + $reportUrl, $outputFile) { $client = $youtubeReporting->getClient(); - // Setting the defer flag to true tells the client to return a request which can be called - // with ->execute(); instead of making the API call immediately. + // Setting the defer flag to true tells the client to return a request that + // can be called with ->execute(); instead of making the API call immediately. $client->setDefer(true); - // Call the YouTube Reporting API's media.download method to download a report. - $request = $youtubeReporting->media->download(""); - $request->setUrl($reportUrl); - $response = $client->execute($request); - - file_put_contents("reportFile", $response->getResponseBody()); + // Call YouTube Reporting API's media.download method to download a report. + $request = $youtubeReporting->media->download('', array('alt' => 'media')); + $request = $request->withUri(new \GuzzleHttp\Psr7\Uri($reportUrl)); + $responseBody = ''; + try { + $response = $client->execute($request); + $responseBody = $response->getBody(); + } catch (Google_Service_Exception $e) { + $responseBody = $e->getTrace()[0]['args'][0]->getResponseBody(); + } + file_put_contents($outputFile, $responseBody); $client->setDefer(false); } -?> - - - -Retrieve reports - - -
-
- Job Id: -
-
-
- Report URL: -
-
-
- - - +// Define an object that will be used to make all API requests. +$client = getClient(); +// YouTube Reporting object used to make YouTube Reporting API requests. +$youtubeReporting = new Google_Service_YouTubeReporting($client); + +if ($CONTENT_OWNER_ID) { + if (!$DOWNLOAD_URL && !$JOB_ID) { + listReportingJobs($youtubeReporting, $CONTENT_OWNER_ID, + $INCLUDE_SYSTEM_MANAGED); + } else if ($JOB_ID) { + listReportsForJob($youtubeReporting, $JOB_ID, $CONTENT_OWNER_ID); + } else if ($DOWNLOAD_URL && $OUTPUT_FILE) { + downloadReport($youtubeReporting, $DOWNLOAD_URL, $OUTPUT_FILE); + } +} + +?> diff --git a/python/README.md b/python/README.md new file mode 100644 index 00000000..77f25069 --- /dev/null +++ b/python/README.md @@ -0,0 +1,283 @@ +## Prerequisites + +* Python 2.6 or greater + +* The pip package management tool + +* The Google APIs Client Library for Python: + ``` + pip install --upgrade google-api-python-client + ``` +* The google-auth, google-auth-oauthlib, and google-auth-httplib2 for user authorization. + ``` + pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2 + ``` + +### Setting up your project and running code samples + +1. Create a project in the API Console and set up credentials for a web application. Set the authorized redirect URIs as appropriate. +2. Save the client_secrets.json file associated with your credentials to a local file. +3. Copy the full code sample to a local file in the same directory as the client_secrets.json file (or modify the sample to correctly identify that file's location. +4. Run the sample from the command line and set command-line arguments as necessary: + + ```python sample.py --arg1=value1 --arg2=value2 ...``` + +5. Most samples print something to STDOUT. You can also check the YouTube website to see the effects of requests that write data, such as requests that create playlists or channel sections. + +## Samples in this directory: + +### [Add a channel section](/python/add_channel_section.py) + +Method: youtube.channelSections.insert
+Description: This sample calls the API's channelSections.insert method to create channel sections. +The code accepts a number of command line arguments that let you specify the section's type, display style, title, position, +and content.

+This sample also updates the channel's +brandingSettings.channel.showBrowseView +property so that the channel displays content in a browse view (rather than a feed view). A channel's sections are only +visible if the channel displays content in a browse view.

More information on channel sections is available in the +YouTube Help Center. + +### [Add a channel subscription](/python/add_subscription.py) + +Method: youtube.subscriptions.insert
+Description: This sample calls the API's subscriptions.insert method to add a subscription to a specified +channel. + +### [Add a featured video](/python/add_featured_video.py) + +Method: youtube.channels.update
+Description: This sample calls the API's channels.update method to set invideoPromotion +properties for the channel. + +### [Create a playlist](/python/playlist_updates.py) + +Method: youtube.playlists.insert
+Description: This sample calls the API's playlists.insert method to create a private playlist owned by the +channel authorizing the request. + +### [Create and manage comments](/python/comment_handling.py) + +Method: youtube.commentThreads.list, youtube.comments.insert, youtube.comments.list, youtube.comments.update, +youtube.comments.setModerationStatus, youtube.comments.markAsSpam, youtube.comments.delete
+Description: This sample demonstrates how to use the following API methods to create and manage comments:
+
    +
  • It calls the commentThreads.list method with the videoId parameter set to retrieve comments +for a video.
  • +
  • It calls the comments.insert method with the parentId parameter set to reply to an existing +comment.
  • +
  • It calls the comments.list method with the parentId parameter to retrieve the comments in the +thread.
  • +
  • It calls the comments.update method with comment in the request body to update a comment.
  • +
  • It calls the comments.setModerationStatus method to set the moderation status of the comment, the +comments.markAsSpam method to mark the comment as spam, and the comments.delete method to +delete the comment, using the id parameter to identify the comment.
  • +
+ +### [Create and manage comment threads](/python/comment_threads.py) + +Method: youtube.commentThreads.insert, youtube.commentThreads.list, youtube.commentThreads.update
+Description: This sample demonstrates how to use the following API methods to create and manage top-level comments:
+
    +
  • It calls the commentThreads.insert method once with the channelId parameter to create a +channel comment and once with the videoId parameter to create a video comment.
  • +
  • It calls the commentThreads.list method once with the channelId parameter to retrieve +channel comments and once with the videoId parameter to retrieve video comments.
  • +
  • It calls the commentThreads.update method once to update a video comment and then again to update a +channel comment. In each case, the request body contains the comment resource being updated.
  • +
+ +### [Create and manage YouTube video caption tracks](/python/captions.py) + +Method: youtube.captions.insert, youtube.captions.list, youtube.captions.update, youtube.captions.download, +youtube.captions.delete
+Description: This sample demonstrates how to use the following API methods to create and manage YouTube video caption +tracks:
+
    +
  • It calls the captions.insert method with the isDraft parameter set to true +to upload a caption track in draft status.
  • +
  • It calls the captions.list method with the videoId parameter to retrieve video caption +tracks.
  • +
  • It calls the captions.update method with the caption in the request body to update a caption track.
  • +
  • It calls the captions.download method to download the caption track.
  • +
  • It calls the captions.delete method to delete the caption track, using the id parameter to +identify the caption track.
  • +
+ +### [Like a video](/python/like_video.py) + +Method: youtube.videos.rate
+Description: This sample calls the API's videos.rate method to set a positive rating for a video. + +### [Post a channel bulletin](/python/channel_bulletin.py) + +Method: youtube.activities.insert
+Description: This sample calls the API's activities.insert method to post a bulletin to the channel +associated with the request. + +### [Remove a watermark image from a channel](/python/unset_watermark.py) + +Method: youtube.watermarks.unset
+Description: This sample calls the API's watermarks.unset method to remove the watermark +image for a channel. The request must be authorized by the channel that owns the video. + +### [Retrieve my uploads](/python/my_uploads.py) + +Method: youtube.playlistItems.list
+Description: This sample calls the API's playlistItems.list method to retrieve a list of videos uploaded +to the channel associated with the request. The code also calls the channels.list method with the +mine parameter set to true to retrieve the playlist ID that identifies the channel's uploaded +videos. + +### [Search by keyword](/python/search.py) + +Method: youtube.search.list
+Description: This sample calls the API's search.list method to retrieve search results associated with +a particular keyword. + +### [Search by location](/python/geolocation_search.py) + +Method: youtube.search.list, youtube.videos.list
+Description: This sample calls the API's search.list method with the type, +q, location, and locationRadius parameters to retrieve search results +matching the provided keyword within the radius centered at a particular location. Using the video ids from +the search result, the sample calls the API's videos.list method to retrieve location details +of each video. + +### [Set and retrieve localized channel metadata](/python/channel_localizations.py) + +Method: youtube.channels.update, youtube.channels.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel:
+
    +
  • It calls the channels.update method to update the default language of a channel's metadata and to add a +localized version of this metadata in a selected language. Note that to set the default language for a channel resource, +you actually need to update the brandingSettings.channel.defaultLanguage property.
  • +
  • It calls the channels.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the channels.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that channel.
  • +
+ +### [Set and retrieve localized channel section metadata](/python/channel_section_localizations.py) + +Method: youtube.channelSections.update, youtube.channelSections.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +channel section:
+
    +
  • It calls the channelSections.update method to update the default language of a channel section's +metadata and to add a localized version of this metadata in a selected language.
  • +
  • It calls the channelSections.list method with the hl parameter set to a specific language +to retrieve localized metadata in that language.
  • +
  • It calls the channelSections.list method and includes localizations in the +part parameter value to retrieve all of the localized metadata for that channel section.
  • +
+ +### [Set and retrieve localized playlist metadata](/python/playlist_localizations.py) + +Method: youtube.playlists.update, youtube.playlists.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata for a +playlist:
+
    +
  • It calls the playlists.update method to update the default language of a playlist's metadata and to add +a localized version of this metadata in a selected language.
  • +
  • It calls the playlists.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the playlists.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that playlist.
  • +
+ +### [Set and retrieve localized video metadata](/python/video_localizations.py) + +Method: youtube.videos.update, youtube.videos.list
+Description: This sample demonstrates how to use the following API methods to set and retrieve localized metadata +for a video:
+
    +
  • It calls the videos.update method to update the default language of a video's metadata and to add +a localized version of this metadata in a selected language.
  • +
  • It calls the videos.list method with the hl parameter set to a specific language to +retrieve localized metadata in that language.
  • +
  • It calls the videos.list method and includes localizations in the part +parameter value to retrieve all of the localized metadata for that video.
  • +
+ +### [Shuffle existing channel sections](/python/shuffle_channel_sections.py) + +Method: youtube.channelSections.list,youtube.channelSections.update
+Description: This sample calls the API's channelSections.list method to get the list of current channel +sections. Then it shuffles the list and calls channelSections.update to change the position of each item.

+More information on channel sections is available in the +YouTube Help Center. + +### [Update a video](/python/update_video.py) + +Method: youtube.videos.update
+Description: This sample calls the API's videos.update method to update a video owned by the channel +authorizing the request. + +### [Upload a banner image and set as channel's banner](/python/upload_banner.py) + +Method: youtube.channelBanners.insert, youtube.channels.update
+Description: This sample calls the API's channelBanners.insert method to upload an image. With the +returned URL, the sample calls channels.update method to update the channel's banner to that image. + +### [Upload a video](/python/upload_video.py) + +Method: youtube.videos.insert
+Description: This sample calls the API's videos.insert method to upload a video to the channel associated +with the request. + +### [Upload a video thumbnail image](/python/upload_thumbnail.py) + +Method: youtube.thumbnails.set
+Description: This sample calls the API's thumbnails.set method to upload an image and set it as the +thumbnail image for a video. The request must be authorized by the channel that owns the video. + +### [Upload a watermark image and set it for a channel](/python/set_watermark.py) + +Method: youtube.watermarks.set
+Description: This sample calls the API's watermarks.set method to upload an image and set it as the +watermark image for a channel. The request must be authorized by the channel that owns the video. + +### [Create a broadcast and stream](/python/create_broadcast.py) + +Method: youtube.liveBroadcasts.bind,youtube.liveBroadcasts.insert,youtube.liveStreams.insert
+Description: This sample calls the API's liveBroadcasts.insert and liveStreams.insert +methods to create a broadcast and a stream. Then, it calls the liveBroadcasts.bind method to bind +the stream to the broadcast. + +### [Retrieve a channel's broadcasts](/python/list_broadcasts.py) + +Method: youtube.liveBroadcasts.list
+Description: This sample calls the API's liveBroadcasts.list method to retrieve a list of broadcasts for +the channel associated with the request. By default, the request retrieves all broadcasts for the channel, but you can +also specify a value for the --broadcast-status option to only retrieve broadcasts with a particular status. + +### [Retrieve a channel's live video streams](/python/list_streams.py) + +Method: youtube.liveStreams.list
+Description: This sample calls the API's liveStreams.list method to retrieve a list of video stream settings +that a channel can use to broadcast live events on YouTube. + +### [Retrieve top 10 videos by viewcount](/python/yt_analytics_report.py) + +Method: youtubeAnalytics.reports.query
+Description: This sample calls the API's reports.query method to retrieve YouTube Analytics data. +By default, the report retrieves the top 10 videos based on viewcounts, and it returns several metrics for those +videos, sorting the results in reverse order by viewcount. By setting command line parameters, you can use the +same code to retrieve other reports as well. + +### [Create a reporting job](/python/create_reporting_job.py) + +Method: youtubeReporting.reportTypes.list, youtubeReporting.jobs.create
+Description: This sample demonstrates how to create a reporting job. It calls the reportTypes.list method +to retrieve a list of available report types. It then calls the jobs.create method to create a new reporting +job. + +### [Retrieve reports](/python/retrieve_reports.py) + +Method: youtubeReporting.jobs.list, youtubeReporting.reports.list
+Description: This sample demonstrates how to retrieve reports created by a specific job. It calls the +jobs.list method to retrieve reporting jobs. It then calls the reports.list method with the +jobId parameter set to a specific job id to retrieve reports created by that job. Finally, the sample +prints out the download URL for each report. diff --git a/python/add_channel_section.py b/python/add_channel_section.py index e51fcf6c..c2b82911 100644 --- a/python/add_channel_section.py +++ b/python/add_channel_section.py @@ -1,15 +1,14 @@ #!/usr/bin/python -import httplib2 +import argparse import os import re -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -23,72 +22,50 @@ # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -SECTION_TYPES = ("allPlaylists", "completedEvents", "likedPlaylists", - "likes", "liveEvents", "multipleChannels", "multiplePlaylists", - "popularUploads", "recentActivity", "recentPosts", "recentUploads", - "singlePlaylist", "upcomingEvents",) -SECTION_STYLES = ("horizontalRow", "verticalList",) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) +SECTION_TYPES = ('allPlaylists', 'completedEvents', 'likedPlaylists', + 'likes', 'liveEvents', 'multipleChannels', 'multiplePlaylists', + 'popularUploads', 'recentActivity', 'recentPosts', 'recentUploads', + 'singlePlaylist', 'upcomingEvents',) +SECTION_STYLES = ('horizontalRow', 'verticalList',) - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +def print_response(response): + print(response) def enable_browse_view(youtube): channels_list_response = youtube.channels().list( - part="brandingSettings", + part='brandingSettings', mine=True ).execute() - channel = channels_list_response["items"][0] - channel["brandingSettings"]["channel"]["showBrowseView"] = True + channel = channels_list_response['items'][0] + channel['brandingSettings']['channel']['showBrowseView'] = True youtube.channels().update( - part="brandingSettings", + part='brandingSettings', body=channel ).execute() def add_channel_section(youtube, args): channels = None if args.channels: - channels = re.split("\s*,\s*", args.channels) + channels = re.split('\s*,\s*', args.channels) playlists = None if args.playlists: - playlists = re.split("\s*,\s*", args.playlists) + playlists = re.split('\s*,\s*', args.playlists) body = dict( snippet=dict( @@ -104,28 +81,31 @@ def add_channel_section(youtube, args): ) youtube.channelSections().insert( - part="snippet,contentDetails", + part='snippet,contentDetails', body=body ).execute() if __name__ == '__main__': - argparser.add_argument("--type", choices=SECTION_TYPES, required=True, - help="The type of the section to be added.") - argparser.add_argument("--style", choices=SECTION_STYLES, required=True, - help="The style of the section to be added.") - argparser.add_argument("--title", - help=("The title to display for the new section. This is only used " - "with the multiplePlaylists or multipleChannels section types.")) - argparser.add_argument("--position", type=int, - help=("The position of the new section. " - "Use 0 for the top, or don't set a value for the bottom.")) - argparser.add_argument("--playlists", - help="One or more playlist ids, comma-separated (e.g. PL...).") - argparser.add_argument("--channels", - help="One or more channel ids, comma-separated (e.g. UC...).") - args = argparser.parse_args() - - youtube = get_authenticated_service(args) + + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('--type', choices=SECTION_TYPES, required=True, + help='The type of the section to be added.') + parser.add_argument('--style', choices=SECTION_STYLES, required=True, + help='The style of the section to be added.') + parser.add_argument('--title', + help='The title to display for the new section. This is only used ' + 'with the multiplePlaylists or multipleChannels section types.') + parser.add_argument('--position', type=int, + help='The position of the new section. Use 0 for the top, ' + 'or don\'t set a value for the bottom.') + parser.add_argument('--playlists', + help='One or more playlist ids, comma-separated (e.g. PL...).') + parser.add_argument('--channels', + help='One or more channel ids, comma-separated (e.g. UC...).') + + args = parser.parse_args() + + youtube = get_authenticated_service() try: # Before channel shelves will appear on your channel's web page, browse # view needs to be enabled. If you know that your channel already has @@ -135,6 +115,6 @@ def add_channel_section(youtube, args): add_channel_section(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "Added new channel section." + print 'Added new channel section. diff --git a/python/add_subscription.py b/python/add_subscription.py index dadaae0b..2ac0510e 100644 --- a/python/add_subscription.py +++ b/python/add_subscription.py @@ -1,14 +1,19 @@ #!/usr/bin/python -import httplib2 +# This code sample shows how to add a channel subscription. +# The default channel-id is for the GoogleDevelopers YouTube channel. +# Sample usage: +# python add_subscription.py --channel-id=UC_x5XG1OV2P6uZZ5FSM9Ttw + +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,46 +26,18 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # This method calls the API's youtube.subscriptions.insert method to add a # subscription to the specified channel. @@ -75,17 +52,18 @@ def add_subscription(youtube, channel_id): ) )).execute() - return add_subscription_response["snippet"]["title"] + return add_subscription_response['snippet']['title'] -if __name__ == "__main__": - argparser.add_argument("--channel-id", help="ID of the channel to subscribe to.", - default="UCtVd0c0tGXuTSbU5d8cSBUg") - args = argparser.parse_args() +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Process arguments.') + parser.add_argument('--channel-id', help='ID of the channel to subscribe to.', + default='UC_x5XG1OV2P6uZZ5FSM9Ttw') + args = parser.parse_args() - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: channel_title = add_subscription(youtube, args.channel_id) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "A subscription to '%s' was added." % channel_title + print 'A subscription to \'%s\' was added.' % channel_title diff --git a/python/batch_report_download.py b/python/batch_report_download.py deleted file mode 100644 index 6a1d28af..00000000 --- a/python/batch_report_download.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/python - -import httplib2 -import os -import sys -import urllib - -from apiclient.discovery import build, build_from_document -from apiclient.errors import HttpError -from apiclient.http import MediaIoBaseDownload -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow - - -# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains -# the OAuth 2.0 information for this application, including its client_id and -# client_secret. You can acquire an OAuth 2.0 client ID and client secret from -# the Google Cloud Console at -# https://cloud.google.com/console. -# Please ensure that you have enabled the YouTube Analytics API for your project. -# For more information about using OAuth2 to access Google APIs, see: -# https://developers.google.com/youtube/v3/guides/authentication -# For more information about the client_secrets.json file format, see: -# https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This OAuth 2.0 access scope allows for full read/write access to the -# authenticated user's account. -SCOPES = ("https://www.googleapis.com/auth/yt-analytics-monetary.readonly", - "https://www.googleapis.com/auth/yt-analytics.readonly") -YOUTUBE_ANALYTICS_API_SERVICE_NAME = "youtubeAnalytics" -YOUTUBE_ANALYTICS_API_VERSION = "v1beta1" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the Cloud Console -https://cloud.google.com/console - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -# Maps a shorthand notation for the different report types to the string used -# to identify each type in the batchReportDefinitionList response. -REPORT_TYPES_TO_NAMES = dict( - assets="Full asset report", - claims="Full claim report" -) - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=" ".join(SCOPES), - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - http = credentials.authorize(httplib2.Http()) - - return build(YOUTUBE_ANALYTICS_API_SERVICE_NAME, - YOUTUBE_ANALYTICS_API_VERSION, http=http) - -def get_available_reports(youtubeAnalytics, contentOwner): - definitions_list_response = youtubeAnalytics.batchReportDefinitions().list( - onBehalfOfContentOwner=contentOwner, - ).execute() - - return definitions_list_response["items"] - -def get_info_for_report(youtubeAnalytics, contentOwner, report_id): - reports_list_response = youtubeAnalytics.batchReports().list( - onBehalfOfContentOwner=contentOwner, - batchReportDefinitionId=report_id - ).execute() - - url = reports_list_response["items"][0]["outputs"][0]["downloadUrl"] - date = reports_list_response["items"][0]["timeSpan"]["startTime"] - return (url, date) - -if __name__ == "__main__": - argparser.add_argument("--content-owner-id", required=True, - help="ID of the content owner.") - argparser.add_argument("--report-type", required=True, - choices=REPORT_TYPES_TO_NAMES.keys(), help="The type of report to download.") - argparser.add_argument("--download-directory", default=os.getcwd(), - help="The directory to download the report into.") - args = argparser.parse_args() - - # Steps to download a batch report: - # 1. Given an authorized instance of the YouTube Analytics service, retrieve - # a list of all the available report definitions via - # youtubeAnalytics.batchReportDefinitions.list() - # 2. Iterate through the report definitions to find the one we're interested - # in based on its name: either an assets or claims report. - # 3. Get the unique id of the report definition, which will in turn be passed - # in to youtubeAnalytics.batchReports.list(). - # 4. The youtubeAnalytics.batchReports.list() reponse will contain one or more - # days' worth of reports. The code gets download info for the first item - # in the response, which will be the most recent day's report. - # 5. Parse out the date and the download URL for the relevant report, and use - # that to download the report, with the date used as part of the file name. - youtubeAnalytics = get_authenticated_service(args) - try: - reports = get_available_reports(youtubeAnalytics, args.content_owner_id) - - report_id = None - for report in reports: - if (REPORT_TYPES_TO_NAMES[args.report_type] == report["name"] - and report["status"] == "supported"): - report_id = report["id"] - break - - if report_id: - (url, date) = get_info_for_report(youtubeAnalytics, - args.content_owner_id, report_id) - - file_path = os.path.join(args.download_directory, - "%s-%s.csv" % (args.report_type, date)) - - # This is a simple approach to downloading a file at a given URL. - # If desired, you can add in a callback method to log the - # progress of the download. See - # http://docs.python.org/2/library/urllib.html#urllib.urlretrieve - urllib.urlretrieve(url, file_path) - - print "The report was downloaded to %s" % file_path - else: - # There might not be a report available if, for instance, there are no - # assets or claims associated with a given content owner's account. - print "No report of type '%s' was available." % args.report_type - except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) diff --git a/python/channel_localizations.py b/python/channel_localizations.py index 6a615e70..a244ca6d 100644 --- a/python/channel_localizations.py +++ b/python/channel_localizations.py @@ -1,17 +1,17 @@ #!/usr/bin/python # Usage example: -# python channel_localizations.py --action='' --channel_id='' --default_language='' --language='' --description='' +# python channel_localizations.py --action='' --channel_id='' --default_language='' --language='' --title='' --description='<description>' -import httplib2 +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -25,73 +25,72 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' # Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # Call the API's channels.update method to update an existing channel's default language, # and localized description in a specific language. -def set_channel_localization(youtube, channel_id, default_language, language, description): +def set_channel_localization(youtube, args): results = youtube.channels().list( - part="brandingSettings,localizations", - id=channel_id + part='localizations', + id=args.channel_id ).execute() - channel = results["items"][0] + channel = results['items'][0] + # Ensure that a value is set for the resource's snippet.defaultLanguage property. # To set the snippet.defaultLanguage property for a channel resource, # you actually need to update the brandingSettings.channel.defaultLanguage property. - channel["brandingSettings"]["channel"]["defaultLanguage"] = default_language - if "localizations" not in channel: - channel["localizations"] = {} - channel["localizations"][language] = { - "description": description + if 'localizations' not in channel: + channel['localizations'] = {} + if args.title and args.description and args.language: + channel['localizations'][args.language] = { + 'title': args.title, + 'description': args.description } + # Set the default language if it is provided as an argument + if args.default_language: + results = youtube.channels().list( + part='brandingSettings', + id=args.channel_id + ).execute() + branding_settings_channel = results['items'][0] + # This property must be removed when changing the default language + # or set to the original channel title to avoid a + # channelTitleUpdateForbidden error. + del branding_settings_channel['brandingSettings']['channel']['title'] + branding_settings_channel['brandingSettings']['channel']['defaultLanguage'] = ( + args.default_language) + language_result = youtube.channels().update( + part='brandingSettings', + body=branding_settings_channel + ).execute() + updated_default_language = ( + language_result['brandingSettings']['channel']['defaultLanguage']) + print 'Updated language to %s' % updated_default_language + update_result = youtube.channels().update( - part="brandingSettings,localizations", + part='localizations', body=channel ).execute() - localization = update_result["localizations"][language] + localization = update_result['localizations'][args.language] - print ("Updated channel '%s' default language to '%s', localized description" - " to '%s' in language '%s'" % (channel_id, localization["description"], language)) + print ('Updated channel \'%s\' localized title and description to ' + '\'%s\' and \'%s\' in language \'%s\'' % + (args.channel_id, localization['title'], localization['description'], args.language)) # Call the API's channels.list method to retrieve an existing channel localization. @@ -99,7 +98,7 @@ def set_channel_localization(youtube, channel_id, default_language, language, de # this method will return text in the default language. def get_channel_localization(youtube, channel_id, language): results = youtube.channels().list( - part="snippet", + part='snippet', id=channel_id, hl=language ).execute() @@ -107,55 +106,60 @@ def get_channel_localization(youtube, channel_id, language): # The localized object contains localized text if the hl parameter specified # a language for which localized text is available. Otherwise, the localized # object will contain metadata in the default language. - localized = results["items"][0]["snippet"]["localized"] + localized = results['items'][0]['snippet']['localized'] - print "Channel description is '%s' in language '%s'" % (localized["description"], language) + print 'Channel description is \'%s\' in language \'%s\'' % (localized['description'], language) # Call the API's channels.list method to list the existing channel localizations. def list_channel_localizations(youtube, channel_id): results = youtube.channels().list( - part="snippet,localizations", + part='snippet,localizations', id=channel_id ).execute() - localizations = results["items"][0]["localizations"] - - for language, localization in localizations.iteritems(): - print "Channel description is '%s' in language '%s'" % (localization["description"], language) - - -if __name__ == "__main__": - # The "action" option specifies the action to be processed. - argparser.add_argument("--action", help="Action") - # The "channel_id" option specifies the ID of the selected YouTube channel. - argparser.add_argument("--channel_id", - help="ID for channel for which the localization will be applied.") - # The "default_language" option specifies the language of the channel's default metadata. - argparser.add_argument("--default_language", help="Default language of the channel to update.", - default="en") - # The "language" option specifies the language of the localization that is being processed. - argparser.add_argument("--language", help="Language of the localization.", default="de") - # The "description" option specifies the localized description of the chanel to be set. - argparser.add_argument("--description", help="Localized description of the channel to be set.", - default="Localized Description") - - args = argparser.parse_args() + if 'localizations' in results['items'][0]: + localizations = results['items'][0]['localizations'] + for language, localization in localizations.iteritems(): + print 'Channel description is \'%s\' in language \'%s\'' % (localization['description'], language) + else: + print 'There aren\'t any localizations for this channel yet.' + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # The 'action' option specifies the action to be processed. + parser.add_argument('--action', help='Action') + # The 'channel_id' option specifies the ID of the selected YouTube channel. + parser.add_argument('--channel_id', + help='ID for channel for which the localization will be applied.') + # The 'default_language' option specifies the language of the channel's default metadata. + parser.add_argument('--default_language', help='Default language of the channel to update.', + default=None) + # The 'language' option specifies the language of the localization that is being processed. + parser.add_argument('--language', help='Language of the localization.', default='de') + # The 'title' option specifies the localized title of the chanel to be set. + parser.add_argument('--title', help='Localized title of the channel to be set.', + default='Localized title') + # The 'description' option specifies the localized description of the chanel to be set. + parser.add_argument('--description', help='Localized description of the channel to be set.', + default='Localized description') + + args = parser.parse_args() if not args.channel_id: - exit("Please specify channel id using the --channel_id= parameter.") + exit('Please specify channel id using the --channel_id= parameter.') - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: if args.action == 'set': - set_channel_localization(youtube, args.channel_id, args.default_language, args.language, args.description) + set_channel_localization(youtube, args) elif args.action == 'get': get_channel_localization(youtube, args.channel_id, args.language) elif args.action == 'list': list_channel_localizations(youtube, args.channel_id) else: - exit("Please specify a valid action using the --action= parameter.") + exit('Please specify a valid action using the --action= parameter.') except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "Set and retrieved localized metadata for a channel." + print 'Set or retrieved localized metadata for a channel.' diff --git a/python/channel_section_localizations.py b/python/channel_section_localizations.py index a20ecf02..e4c2658f 100644 --- a/python/channel_section_localizations.py +++ b/python/channel_section_localizations.py @@ -1,17 +1,22 @@ #!/usr/bin/python -# Usage example: +# Retrieve or set localized channel section metadata. +# See the Prerequisites section in the README file in this directory +# for more information about running this sample locally. + +# Usage examples: +# python channel_sections_localizations.py --action=list --mine=True # python channel_sections_localizations.py --action='<action>' --channel_section_id='<channel_section_id>' --default_language='<default_language>' --language='<language>' --title='<title>' -import httplib2 +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -25,135 +30,184 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -# Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +ACTIONS = ('get', 'list', 'set') +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # Call the API's channelSections.update method to update an existing channel section's # default language, and localized title in a specific language. -def set_channel_section_localization(youtube, channel_section_id, default_language, language, title): +def set_channel_section_localization(youtube, args): + + # Retrieve the snippet and localizations for the channel section. results = youtube.channelSections().list( - part="snippet,localizations", - id=channel_section_id + part='snippet,contentDetails,localizations', + id=args.channel_section_id ).execute() - channel_section = results["items"][0] - # Ensure that a value is set for the resource's snippet.defaultLanguage property. - channel_section["snippet"]["defaultLanguage"] = default_language - if "localizations" not in channel_section: - channel_section["localizations"] = {} - channel_section["localizations"][language] = { - "title": title - } + channel_section = results['items'][0] + + # If the language argument is set, set the localized title for that language. + # The "title" argument has a default value to make the script simpler to run + # as a demo. But in an actual app, you would likely want to set its value. + if args.language and args.language != '': + if 'localizations' not in channel_section: + channel_section['localizations'] = {} + + channel_section['localizations'][args.language] = { + 'title': args.title, + } + + # If the default language is set AND there is localized metadata for that + # language, set the channel section's title and description to match the + # localized title and description for the newly set default language. + if args.default_language: + channel_section['snippet']['defaultLanguage'] = args.default_language + if args.default_language in channel_section['localizations']: + channel_section['snippet']['title'] = ( + channel_section['localizations'][args.default_language]['title']) update_result = youtube.channelSections().update( - part="snippet,localizations", + part='snippet,contentDetails,localizations', body=channel_section ).execute() - localization = update_result["localizations"][language] - - print ("Updated channel section '%s' default language to '%s', localized title" - " to '%s' in language '%s'" % (channel_section_id, localization["title"], language)) - + if args.language: + for language in update_result['localizations']: + # Languages with locales, like "pt-br" are returned as pt-BR in metadata. + # This ensures that the language specified when running the script can be + # matched to the language returned in the metadata. + if language.lower() == args.language.lower(): + localization = update_result['localizations'][args.language] + print ('Updated channel section %s localized title to %s in \'%s\'.' % + (args.channel_section_id, localization['title'], args.language)) + break + + if (args.default_language and + args.default_language == update_result['snippet']['defaultLanguage']): + print 'Updated default language to %s.' % args.default_language # Call the API's channelSections.list method to retrieve an existing channel section localization. # If the localized text is not available in the requested language, # this method will return text in the default language. -def get_channel_section_localization(youtube, channel_section_id, language): +def get_channel_section_localization(youtube, args): results = youtube.channelSections().list( - part="snippet", - id=channel_section_id, - hl=language + part='snippet', + id=args.channel_section_id, + hl=args.language ).execute() # The localized object contains localized text if the hl parameter specified # a language for which localized text is available. Otherwise, the localized # object will contain metadata in the default language. - localized = results["items"][0]["snippet"]["localized"] - - print "Channel section title is '%s' in language '%s'" % (localized["title"], language) + localized = results['items'][0]['snippet']['localized'] + print('The channel section\'s title is \'%s\' in language \'%s\'.' % + (localized['title'], language)) -# Call the API's channelSections.list method to list the existing channel section localizations. -def list_channel_section_localizations(youtube, channel_section_id): +# Call the API's channelSections.list method to list all existing localizations +# for the channel section. +def list_channel_section_localizations(youtube, args): results = youtube.channelSections().list( - part="snippet,localizations", - id=channel_section_id + part='snippet,localizations', + id=args.channel_section_id ).execute() - localizations = results["items"][0]["localizations"] - - for language, localization in localizations.iteritems(): - print "Channel section title is '%s' in language '%s'" % (localization["title"], language) - - -if __name__ == "__main__": - # The "action" option specifies the action to be processed. - argparser.add_argument("--action", help="Action") - # The "channel_section_id" option specifies the ID of the selected YouTube channel section. - argparser.add_argument("--channel_section_id", - help="ID for channel section for which the localization will be applied.") - # The "default_language" option specifies the language of the channel section's default metadata. - argparser.add_argument("--default_language", - help="Default language of the channel section to update.", default="en") - # The "language" option specifies the language of the localization that is being processed. - argparser.add_argument("--language", help="Language of the localization.", default="de") - # The "title" option specifies the localized title of the channel section to be set. - argparser.add_argument("--title", help="Localized title of the channel section to be set.", - default="Localized Title") + if 'localizations' in results['items'][0]: + localizations = results['items'][0]['localizations'] - args = argparser.parse_args() + for language, localization in localizations.iteritems(): + print('The channel section title is \'%s\' in language \'%s\'.' % + (localization['title'], language)) + else: + print 'This channel section does not have localizations yet.' - if not args.channel_section_id: - exit("Please specify channel section id using the --channel_section_id= parameter.") +# Call the API's channelSections.list method to list localizations for all +# channel sections in the authorizing user\'s channel. This function might +# be called as a way of identifying the ID of the section you actually want +# to update. +def list_my_channel_section_localizations(youtube, args): + results = youtube.channelSections().list( + part='snippet,localizations', + mine=True, + ).execute() - youtube = get_authenticated_service(args) + print(results) + + for i in range(0, len(results['items'])): + item = results['items'][i] + print str(item['snippet']['position']) + ':' + print ' ID: ' + item['id'] + print ' Type: ' + item['snippet']['type'] + if ('title' in item['snippet'] and item['snippet']['title']): + print ' Title: ' + str(item['snippet']['title']) + + if 'localizations' in results['items'][i]: + localizations = results['items'][i]['localizations'] + print(' Localized titles by language:') + for language, localization in localizations.iteritems(): + print(' ' + language + ': ' + localization['title']) + else: + print(' No localizations. :(\n') + + #for language, localization in localizations.iteritems(): + # print('The channel section title is \'%s\' in language \'%s\'.' % + # (localization['title'], language)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # The 'action' option specifies the action to be processed. + parser.add_argument('--action', choices=ACTIONS, required=True, + help='The type of operation. Supported values are: "get", "list", "set"') + # The ID of the channel section for which data is being retrieved or updated. + parser.add_argument('--channel_section_id', + help='ID of channel section for which localized data is being ' + + 'retrieved or updated.') + # The language of the channel section's default metadata. + parser.add_argument('--default_language', + help='Default language of the channel section to update.') + # The language of the localization that is being processed. + parser.add_argument('--language', help='Language of the localized metadata.') + # The localized channel section title for the specified language. + parser.add_argument('--title', + help='Localized title of the channel section to be set.', + default='Localized Title') + # The language of the channel section's default metadata. + parser.add_argument('--mine', type=bool, default=False, + help='List localizations for all of my channel sections.') + + args = parser.parse_args() + + if not args.channel_section_id and not args.mine: + exit('You must either specify a channel section ID using the ' + + '--channel_section_id argument or retrieve localizations ' + + 'for all of your channel sections by setting the --mine ' + + 'argument to True.') + + youtube = get_authenticated_service() try: if args.action == 'set': - set_channel_section_localization(youtube, args.channel_section_id, args.default_language, args.language, args.title) + set_channel_section_localization(youtube, args) elif args.action == 'get': - get_channel_section_localization(youtube, args.channel_section_id, args.language) + get_channel_section_localization(youtube, args) elif args.action == 'list': - list_channel_section_localizations(youtube, args.channel_section_id) + if args.mine: + list_my_channel_section_localizations(youtube, args) + else: + list_channel_section_localizations(youtube, args) else: - exit("Please specify a valid action using the --action= parameter.") + exit('Please specify a valid action using the --action= parameter.') except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - else: - print "Set and retrieved localized metadata for a channel section." + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/create_reporting_job.py b/python/create_reporting_job.py index 23d14ea7..0fd821c1 100644 --- a/python/create_reporting_job.py +++ b/python/create_reporting_job.py @@ -1,17 +1,20 @@ #!/usr/bin/python +# Create a reporting job for the authenticated user's channel or +# for a content owner that the user's account is linked to. # Usage example: # python create_reporting_job.py --name='<name>' +# python create_reporting_job.py --content-owner='<CONTENT OWNER ID>' +# python create_reporting_job.py --content-owner='<CONTENT_OWNER_ID>' --report-type='<REPORT_TYPE_ID>' --name='<REPORT_NAME>' -import httplib2 +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -25,98 +28,109 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for read access to the YouTube Analytics monetary reports for # authenticated user's account. Any request that retrieves earnings or ad performance metrics must # use this scope. -YOUTUBE_ANALYTICS_MONETARY_READ_SCOPE = ( - "https://www.googleapis.com/auth/yt-analytics-monetary.readonly") -YOUTUBE_REPORTING_API_SERVICE_NAME = "youtubereporting" -YOUTUBE_REPORTING_API_VERSION = "v1" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +SCOPES = ['https://www.googleapis.com/auth/yt-analytics-monetary.readonly'] +API_SERVICE_NAME = 'youtubereporting' +API_VERSION = 'v1' # Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_ANALYTICS_MONETARY_READ_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_REPORTING_API_SERVICE_NAME, YOUTUBE_REPORTING_API_VERSION, - http=credentials.authorize(httplib2.Http())) - +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +# Remove keyword arguments that are not set. +def remove_empty_kwargs(**kwargs): + good_kwargs = {} + if kwargs is not None: + for key, value in kwargs.iteritems(): + if value: + good_kwargs[key] = value + return good_kwargs # Call the YouTube Reporting API's reportTypes.list method to retrieve report types. -def list_report_types(youtube_reporting): - results = youtube_reporting.reportTypes().list().execute() - reportTypes = results["reportTypes"] - - if "reportTypes" in results and results["reportTypes"]: - reportTypes = results["reportTypes"] +def list_report_types(youtube_reporting, **kwargs): + # Provide keyword arguments that have values as request parameters. + kwargs = remove_empty_kwargs(**kwargs) + results = youtube_reporting.reportTypes().list(**kwargs).execute() + reportTypes = results['reportTypes'] + + if 'reportTypes' in results and results['reportTypes']: + reportTypes = results['reportTypes'] for reportType in reportTypes: - print "Report type id: %s\n name: %s\n" % (reportType["id"], reportType["name"]) + print 'Report type id: %s\n name: %s\n' % (reportType['id'], reportType['name']) else: - print "No report types found" + print 'No report types found' return False return True # Call the YouTube Reporting API's jobs.create method to create a job. -def create_reporting_job(youtube_reporting, report_type_id, name): +def create_reporting_job(youtube_reporting, report_type_id, **kwargs): + # Provide keyword arguments that have values as request parameters. + kwargs = remove_empty_kwargs(**kwargs) + reporting_job = youtube_reporting.jobs().create( body=dict( - reportTypeId=report_type_id, - name=name - ) + reportTypeId=args.report_type, + name=args.name + ), + **kwargs ).execute() - print ("Reporting job '%s' created for reporting type '%s' at '%s'" - % (reporting_job["name"], reporting_job["reportTypeId"], - reporting_job["createTime"])) + print ('Reporting job "%s" created for reporting type "%s" at "%s"' + % (reporting_job['name'], reporting_job['reportTypeId'], + reporting_job['createTime'])) # Prompt the user to enter a report type id for the job. Then return the id. def get_report_type_id_from_user(): - report_type_id = raw_input("Please enter the reportTypeId for the job: ") - print ("You chose '%s' as the report type Id for the job." % report_type_id) + report_type_id = raw_input('Please enter the reportTypeId for the job: ') + print ('You chose "%s" as the report type Id for the job.' % report_type_id) return report_type_id +# Prompt the user to set a job name +def prompt_user_to_set_job_name(): + job_name = raw_input('Please set a name for the job: ') + print ('Great! "%s" is a memorable name for this job.' % job_name) + return job_name + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # The 'name' option specifies the name that will be used for the reporting job. + parser.add_argument('--content-owner', default='', + help='ID of content owner for which you are retrieving jobs and reports.') + parser.add_argument('--include-system-managed', default=False, + help='Whether the API response should include system-managed reports') + parser.add_argument('--name', default='', + help='Name for the reporting job. The script prompts you to set a name ' + + 'for the job if you do not provide one using this argument.') + parser.add_argument('--report-type', default=None, + help='The type of report for which you are creating a job.') + args = parser.parse_args() + + youtube_reporting = get_authenticated_service() -if __name__ == "__main__": - # The "name" option specifies the name that will be used for the reporting job. - argparser.add_argument("--name", - help="Required; name for the reporting job.") - args = argparser.parse_args() - - if not args.name: - exit("Please specify name using the --name= parameter.") - - youtube_reporting = get_authenticated_service(args) try: - if list_report_types(youtube_reporting): - create_reporting_job(youtube_reporting, get_report_type_id_from_user(), args.name) + # Prompt user to select report type if they didn't set one on command line. + if not args.report_type: + if list_report_types(youtube_reporting, + onBehalfOfContentOwner=args.content_owner, + includeSystemManaged=args.include_system_managed): + args.report_type = get_report_type_id_from_user() + # Prompt user to set job name if not set on command line. + if not args.name: + args.name = prompt_user_to_set_job_name() + # Create the job. + if args.report_type: + create_reporting_job(youtube_reporting, + args, + onBehalfOfContentOwner=args.content_owner) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - else: - print "Created reporting job." + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/geolocation_search.py b/python/geolocation_search.py index c051943d..922f7048 100644 --- a/python/geolocation_search.py +++ b/python/geolocation_search.py @@ -1,17 +1,25 @@ #!/usr/bin/python -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.tools import argparser +# This sample executes a search request for the specified search term. +# Sample usage: +# python geolocation_search.py --q=surfing --location-"37.42307,-122.08427" --location-radius=50km --max-results=10 +# NOTE: To use the sample, you must provide a developer key obtained +# in the Google APIs Console. Search for "REPLACE_ME" in this code +# to find the correct place to provide that key.. + +import argparse + +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError # Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps # tab of # https://cloud.google.com/console # Please ensure that you have enabled the YouTube Data API for your project. -DEVELOPER_KEY = "REPLACE_ME" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" +DEVELOPER_KEY = 'REPLACE_ME' +YOUTUBE_API_SERVICE_NAME = 'youtube' +YOUTUBE_API_VERSION = 'v3' def youtube_search(options): youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, @@ -21,19 +29,19 @@ def youtube_search(options): # query term. search_response = youtube.search().list( q=options.q, - type="video", + type='video', location=options.location, locationRadius=options.location_radius, - part="id,snippet", + part='id,snippet', maxResults=options.max_results ).execute() search_videos = [] # Merge video ids - for search_result in search_response.get("items", []): - search_videos.append(search_result["id"]["videoId"]) - video_ids = ",".join(search_videos) + for search_result in search_response.get('items', []): + search_videos.append(search_result['id']['videoId']) + video_ids = ','.join(search_videos) # Call the videos.list method to retrieve location details for each video. video_response = youtube.videos().list( @@ -44,23 +52,23 @@ def youtube_search(options): videos = [] # Add each result to the list, and then display the list of matching videos. - for video_result in video_response.get("items", []): - videos.append("%s, (%s,%s)" % (video_result["snippet"]["title"], - video_result["recordingDetails"]["location"]["latitude"], - video_result["recordingDetails"]["location"]["longitude"])) + for video_result in video_response.get('items', []): + videos.append('%s, (%s,%s)' % (video_result['snippet']['title'], + video_result['recordingDetails']['location']['latitude'], + video_result['recordingDetails']['location']['longitude'])) - print "Videos:\n", "\n".join(videos), "\n" + print 'Videos:\n', '\n'.join(videos), '\n' -if __name__ == "__main__": - argparser.add_argument("--q", help="Search term", default="Google") - argparser.add_argument("--location", help="Location", default="37.42307,-122.08427") - argparser.add_argument("--location-radius", help="Location radius", default="5km") - argparser.add_argument("--max-results", help="Max results", default=25) - args = argparser.parse_args() +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--q', help='Search term', default='Google') + parser.add_argument('--location', help='Location', default='37.42307,-122.08427') + parser.add_argument('--location-radius', help='Location radius', default='5km') + parser.add_argument('--max-results', help='Max results', default=25) + args = parser.parse_args() try: youtube_search(args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/like_video.py b/python/like_video.py index 36a988f5..4f0899a6 100644 --- a/python/like_video.py +++ b/python/like_video.py @@ -1,14 +1,18 @@ #!/usr/bin/python -import httplib2 +# This sample shows how to rate a video. +# Sample usage: +# python like_video.py --videoId=OE63BYWdqC4 --rating=like + +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,64 +25,45 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - -# Add the video rating. This code sets the rating to "like," but you could -# also support an additional option that supports values of "like" and -# "dislike." -def like_video(youtube, video_id): +SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +RATINGS = ('like', 'dislike', 'none') + +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +# Add the video rating. This code sets the rating to 'like,' but you could +# also support an additional option that supports values of 'like' and +# 'dislike.' +def like_video(youtube, args): youtube.videos().rate( - id=video_id, - rating="like" + id=args.videoId, + rating=args.rating ).execute() -if __name__ == "__main__": - argparser.add_argument("--videoid", default="L-oNKK1CrnU", - help="ID of video to like.") - args = argparser.parse_args() +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--videoId', default='OE63BYWdqC4', + help='ID of video to like.') + parser.add_argument('--rating', default='like', + choices=RATINGS, + help='Indicates whether the rating is "like", "dislike", or "none".') + args = parser.parse_args() - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: - like_video(youtube, args.videoid) + like_video(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "%s has been liked." % args.videoid + print ('The %s rating has been added for video ID %s.' % + (args.rating, args.videoId)) diff --git a/python/list_broadcasts.py b/python/list_broadcasts.py index 421d1f66..67e44ab4 100644 --- a/python/list_broadcasts.py +++ b/python/list_broadcasts.py @@ -1,14 +1,14 @@ #!/usr/bin/python -import httplib2 +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,74 +21,49 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for read-only access to the authenticated # user's account, but not other types of account access. -YOUTUBE_READONLY_SCOPE = "https://www.googleapis.com/auth/youtube.readonly" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" +SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 +VALID_BROADCAST_STATUSES = ('all', 'active', 'completed', 'upcoming',) -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -VALID_BROADCAST_STATUSES = ("all", "active", "completed", "upcoming",) - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_READONLY_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # Retrieve a list of broadcasts with the specified status. def list_broadcasts(youtube, broadcast_status): - print "Broadcasts with status '%s':" % broadcast_status + print 'Broadcasts with status "%s":' % broadcast_status list_broadcasts_request = youtube.liveBroadcasts().list( broadcastStatus=broadcast_status, - part="id,snippet", + part='id,snippet', maxResults=50 ) while list_broadcasts_request: list_broadcasts_response = list_broadcasts_request.execute() - for broadcast in list_broadcasts_response.get("items", []): - print "%s (%s)" % (broadcast["snippet"]["title"], broadcast["id"]) + for broadcast in list_broadcasts_response.get('items', []): + print '%s (%s)' % (broadcast['snippet']['title'], broadcast['id']) list_broadcasts_request = youtube.liveBroadcasts().list_next( list_broadcasts_request, list_broadcasts_response) -if __name__ == "__main__": - argparser.add_argument("--broadcast-status", help="Broadcast status", +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--broadcast-status', help='Broadcast status', choices=VALID_BROADCAST_STATUSES, default=VALID_BROADCAST_STATUSES[0]) - args = argparser.parse_args() + args = parser.parse_args() - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: list_broadcasts(youtube, args.broadcast_status) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/list_streams.py b/python/list_streams.py index 095febae..14090fa9 100644 --- a/python/list_streams.py +++ b/python/list_streams.py @@ -1,14 +1,13 @@ #!/usr/bin/python -import httplib2 import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,71 +20,43 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for read-only access to the authenticated # user's account, but not other types of account access. -YOUTUBE_READONLY_SCOPE = "https://www.googleapis.com/auth/youtube.readonly" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_READONLY_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - +SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + # Retrieve a list of the liveStream resources associated with the currently # authenticated user's channel. def list_streams(youtube): - print "Live streams:" + print 'Live streams:' list_streams_request = youtube.liveStreams().list( - part="id,snippet", + part='id,snippet', mine=True, maxResults=50 - ) - + ) + while list_streams_request: list_streams_response = list_streams_request.execute() - - for stream in list_streams_response.get("items", []): - print "%s (%s)" % (stream["snippet"]["title"], stream["id"]) - + + for stream in list_streams_response.get('items', []): + print '%s (%s)' % (stream['snippet']['title'], stream['id']) + list_streams_request = youtube.liveStreams().list_next( list_streams_request, list_streams_response) - -if __name__ == "__main__": - args = argparser.parse_args() - - youtube = get_authenticated_service(args) + +if __name__ == '__main__': + youtube = get_authenticated_service() try: list_streams(youtube) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/my_uploads.py b/python/my_uploads.py index d4e9af2e..0480f24d 100644 --- a/python/my_uploads.py +++ b/python/my_uploads.py @@ -1,13 +1,18 @@ #!/usr/bin/python -import httplib2 +# Retrieve the authenticated user's uploaded videos. +# Sample usage: +# python my_uploads.py + +import argparse import os -import sys +import re -from apiclient.discovery import build -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -20,77 +25,63 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for read-only access to the authenticated # user's account, but not other types of account access. -YOUTUBE_READONLY_SCOPE = "https://www.googleapis.com/auth/youtube.readonly" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - message=MISSING_CLIENT_SECRETS_MESSAGE, - scope=YOUTUBE_READONLY_SCOPE) - -storage = Storage("%s-oauth2.json" % sys.argv[0]) -credentials = storage.get() - -if credentials is None or credentials.invalid: - flags = argparser.parse_args() - credentials = run_flow(flow, storage, flags) - -youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - -# Retrieve the contentDetails part of the channel resource for the -# authenticated user's channel. -channels_response = youtube.channels().list( - mine=True, - part="contentDetails" -).execute() - -for channel in channels_response["items"]: - # From the API response, extract the playlist ID that identifies the list - # of videos uploaded to the authenticated user's channel. - uploads_list_id = channel["contentDetails"]["relatedPlaylists"]["uploads"] - - print "Videos in list %s" % uploads_list_id - +SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +def get_my_uploads_list(): + # Retrieve the contentDetails part of the channel resource for the + # authenticated user's channel. + channels_response = youtube.channels().list( + mine=True, + part='contentDetails' + ).execute() + + for channel in channels_response['items']: + # From the API response, extract the playlist ID that identifies the list + # of videos uploaded to the authenticated user's channel. + return channel['contentDetails']['relatedPlaylists']['uploads'] + + return None + +def list_my_uploaded_videos(uploads_playlist_id): # Retrieve the list of videos uploaded to the authenticated user's channel. playlistitems_list_request = youtube.playlistItems().list( - playlistId=uploads_list_id, - part="snippet", - maxResults=50 + playlistId=uploads_playlist_id, + part='snippet', + maxResults=5 ) + print 'Videos in list %s' % uploads_playlist_id while playlistitems_list_request: playlistitems_list_response = playlistitems_list_request.execute() # Print information about each video. - for playlist_item in playlistitems_list_response["items"]: - title = playlist_item["snippet"]["title"] - video_id = playlist_item["snippet"]["resourceId"]["videoId"] - print "%s (%s)" % (title, video_id) + for playlist_item in playlistitems_list_response['items']: + title = playlist_item['snippet']['title'] + video_id = playlist_item['snippet']['resourceId']['videoId'] + print '%s (%s)' % (title, video_id) playlistitems_list_request = youtube.playlistItems().list_next( playlistitems_list_request, playlistitems_list_response) - print +if __name__ == '__main__': + youtube = get_authenticated_service() + try: + uploads_playlist_id = get_my_uploads_list() + if uploads_playlist_id: + list_my_uploaded_videos(uploads_playlist_id) + else: + print('There is no uploaded videos playlist for this user.') + except HttpError, e: + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/playlist_localizations.py b/python/playlist_localizations.py index 0152f92f..f34007be 100644 --- a/python/playlist_localizations.py +++ b/python/playlist_localizations.py @@ -1,17 +1,16 @@ #!/usr/bin/python # Usage example: -# python playlist_localizations.py --action='<action>' --playlist_id='<playlist_id>' --defaultlanguage='<default_language>' --language='<language>' --title='<title>' --description='<description>' +# python playlist_localizations.py --action='<action>' --playlist_id='<playlist_id>' --default_language='<default_language>' --language='<language>' --title='<title>' --description='<description>' -import httplib2 +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -25,142 +24,154 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -# Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +ACTIONS = ('get', 'list', 'set') +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # Call the API's playlists.update method to update an existing playlist's default language, # localized title and description in a specific language. -def set_playlist_localization(youtube, playlist_id, default_language, language, title, description): +def set_playlist_localization(youtube, args): + results = youtube.playlists().list( - part="snippet,localizations", - id=playlist_id + part='snippet,localizations', + id=args.playlist_id ).execute() - playlist = results["items"][0] - # Ensure that a value is set for the resource's snippet.defaultLanguage property. - playlist["snippet"]["defaultLanguage"] = default_language - if "localizations" not in playlist: - playlist["localizations"] = {} - playlist["localizations"][language] = { - "title": title, - "description": description - } - + playlist = results['items'][0] + + # If the language argument is set, set the localized title and description + # for that language. The "title" and "description" arguments have default + # values to make the script simpler to run as a demo. In an actual app, you + # would likely want to set those arguments also. + if args.language and args.language != '': + if 'localizations' not in playlist: + playlist['localizations'] = {} + + playlist['localizations'][args.language] = { + 'title': args.title, + 'description': args.description + } + + # If the default language is set AND there is localized metadata for that + # language, set the video's title and description to match the localized + # title and description for the newly set default language. + if args.default_language: + playlist['snippet']['defaultLanguage'] = args.default_language + if args.default_language in playlist['localizations']: + playlist['snippet']['title'] = ( + playlist['localizations'][args.default_language]['title']) + playlist['snippet']['description'] = ( + playlist['localizations'][args.default_language]['description']) + + print(playlist) + + # Update the playlist resource update_result = youtube.playlists().update( - part="snippet,localizations", + part='snippet,localizations', body=playlist ).execute() - localization = update_result["localizations"][language] - - print ("Updated playlist '%s' default language to '%s', localized title to '%s'" - " and description to '%s' in language '%s'" - % (playlist_id, localization["title"], localization["description"], language)) - + # Print the actions taken by running the script. + if args.language: + for language in update_result['localizations']: + # Languages with locales, like "pt-br" are returned as pt-BR in metadata. + # This ensures that the language specified when running the script can be + # matched to the language returned in the metadata. + if language.lower() == args.language.lower(): + localization = update_result['localizations'][args.language] + print ('Updated playlist \'%s\' localized title to \'%s\'' + ' and description to \'%s\' in language \'%s\'' % + (args.playlist_id, + localization['title'], + localization['description'], + args.language)) + break + + if (args.default_language and + args.default_language == update_result['snippet']['defaultLanguage']): + print 'Updated default language to %s' % args.default_language # Call the API's playlists.list method to retrieve an existing playlist localization. # If the localized text is not available in the requested language, # this method will return text in the default language. -def get_playlist_localization(youtube, playlist_id, language): +def get_playlist_localization(youtube, args): results = youtube.playlists().list( - part="snippet", - id=playlist_id, - hl=language + part='snippet', + id=args.playlist_id, + hl=args.language ).execute() # The localized object contains localized text if the hl parameter specified # a language for which localized text is available. Otherwise, the localized # object will contain metadata in the default language. - localized = results["items"][0]["snippet"]["localized"] + localized = results['items'][0]['snippet']['localized'] - print ("Playlist title is '%s' and description is '%s' in language '%s'" - % (localized["title"], localized["description"], language)) + print ('Playlist title is "%s" and description is "%s" in language "%s"' + % (localized['title'], localized['description'], args.language)) -# Call the API's playlists.list method to list the existing playlist localizations. -def list_playlist_localizations(youtube, playlist_id): +# Call the API's playlists.list method to list existing localizations +# for the playlist. +def list_playlist_localizations(youtube, args): results = youtube.playlists().list( - part="snippet,localizations", - id=playlist_id + part='snippet,localizations', + id=args.playlist_id ).execute() - localizations = results["items"][0]["localizations"] - - for language, localization in localizations.iteritems(): - print ("Playlist title is '%s' and description is '%s' in language '%s'" - % (localization["title"], localization["description"], language)) - - -if __name__ == "__main__": - # The "action" option specifies the action to be processed. - argparser.add_argument("--action", help="Action") - # The "playlist_id" option specifies the ID of the selected YouTube playlist. - argparser.add_argument("--playlist_id", - help="ID for playlist for which the localization will be applied.") - # The "default_language" option specifies the language of the playlist's default metadata. - argparser.add_argument("--default_language", help="Default language of the playlist to update.", - default="en") - # The "language" option specifies the language of the localization that is being processed. - argparser.add_argument("--language", help="Language of the localization.", default="de") - # The "title" option specifies the localized title of the playlist to be set. - argparser.add_argument("--title", help="Localized title of the playlist to be set.", - default="Localized Title") - # The "description" option specifies the localized description of the playlist to be set. - argparser.add_argument("--description", help="Localized description of the playlist to be set.", - default="Localized Description") - - args = argparser.parse_args() - - if not args.playlist_id: - exit("Please specify playlist id using the --playlist_id= parameter.") - - youtube = get_authenticated_service(args) + if 'localizations' in results['items'][0]: + localizations = results['items'][0]['localizations'] + + for language, localization in localizations.iteritems(): + print ('Playlist title is "%s" and description is "%s" in language "%s"' + % (localization['title'], localization['description'], language)) + else: + print 'There aren\'t any localizations for this playlist yet.' + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # The action to be processed: 'get', 'list', and 'set' are supported. + parser.add_argument('--action', required=True, help='Action', choices=ACTIONS) + # The ID of the selected YouTube olaylist. + parser.add_argument('--playlist_id', + help='The playlist ID for which localizations are being set or retrieved.', + required=True) + # The langauge of the playlist's default metadata. + parser.add_argument('--default_language', + help='Default language to set for the playlist.') + # The language of the localization that is being set or retrieved. + parser.add_argument('--language', help='Language of the localization.') + # The localized title to set in the specified language. + parser.add_argument('--title', + help='Localized title to be set for the playlist.', + default='Localized Title') + # The localized description to set in the specified language. + parser.add_argument('--description', + help='Localized description to be set for the playlist.', + default='Localized Description') + + args = parser.parse_args() + + youtube = get_authenticated_service() + try: if args.action == 'set': - set_playlist_localization(youtube, args.playlist_id, args.default_language, args.language, args.title, args.description) + set_playlist_localization(youtube, args) elif args.action == 'get': - get_playlist_localization(youtube, args.playlist_id, args.language) + get_playlist_localization(youtube, args) elif args.action == 'list': - list_playlist_localizations(youtube, args.playlist_id) - else: - exit("Please specify a valid action using the --action= parameter.") + list_playlist_localizations(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - else: - print "Set and retrieved localized metadata for a playlist." + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/playlist_updates.py b/python/playlist_updates.py index 60e14158..b1ab0504 100644 --- a/python/playlist_updates.py +++ b/python/playlist_updates.py @@ -1,14 +1,18 @@ #!/usr/bin/python -import httplib2 +# This code sample creates a private playlist in the authorizing user's +# YouTube channel. +# Usage: +# python playlist_updates.py --title=<TITLE> --description=<DESCRIPTION> + +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,58 +25,54 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) + +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - message=MISSING_CLIENT_SECRETS_MESSAGE, - scope=YOUTUBE_READ_WRITE_SCOPE) - -storage = Storage("%s-oauth2.json" % sys.argv[0]) -credentials = storage.get() - -if credentials is None or credentials.invalid: - flags = argparser.parse_args() - credentials = run_flow(flow, storage, flags) - -youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - -# This code creates a new, private playlist in the authorized user's channel. -playlists_insert_response = youtube.playlists().insert( - part="snippet,status", - body=dict( +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +def add_playlist(youtube, args): + + body = dict( snippet=dict( - title="Test Playlist", - description="A private playlist created with the YouTube API v3" + title=args.title, + description=args.description ), status=dict( - privacyStatus="private" - ) - ) -).execute() - -print "New playlist id: %s" % playlists_insert_response["id"] + privacyStatus='private' + ) + ) + + playlists_insert_response = youtube.playlists().insert( + part='snippet,status', + body=body + ).execute() + + print 'New playlist ID: %s' % playlists_insert_response['id'] + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument('--title', + default='Test Playlist', + help='The title of the new playlist.') + parser.add_argument('--description', + default='A private playlist created with the YouTube Data API.', + help='The description of the new playlist.') + + args = parser.parse_args() + + youtube = get_authenticated_service() + try: + add_playlist(youtube, args) + except HttpError, e: + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/quickstart.py b/python/quickstart.py new file mode 100644 index 00000000..de2aa315 --- /dev/null +++ b/python/quickstart.py @@ -0,0 +1,44 @@ +# Sample Python code for user authorization + +import os + +import google.oauth2.credentials + +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow + +# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains +# the OAuth 2.0 information for this application, including its client_id and +# client_secret. +CLIENT_SECRETS_FILE = "client_secret.json" + +# This OAuth 2.0 access scope allows for full read/write access to the +# authenticated user's account and requires requests to use an SSL connection. +SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +def channels_list_by_username(service, **kwargs): + results = service.channels().list( + **kwargs + ).execute() + + print('This channel\'s ID is %s. Its title is %s, and it has %s views.' % + (results['items'][0]['id'], + results['items'][0]['snippet']['title'], + results['items'][0]['statistics']['viewCount'])) + +if __name__ == '__main__': + # When running locally, disable OAuthlib's HTTPs verification. When + # running in production *do not* leave this option enabled. + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' + service = get_authenticated_service() + channels_list_by_username(service, + part='snippet,contentDetails,statistics', + forUsername='GoogleDevelopers') diff --git a/python/quickstart_web.py b/python/quickstart_web.py new file mode 100644 index 00000000..ec58f56f --- /dev/null +++ b/python/quickstart_web.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +import os + +import flask + +import google.oauth2.credentials +import google_auth_oauthlib.flow +import googleapiclient.discovery + +# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains +# the OAuth 2.0 information for this application, including its client_id and +# client_secret. +CLIENT_SECRETS_FILE = "client_secret.json" + +# This OAuth 2.0 access scope allows for full read/write access to the +# authenticated user's account and requires requests to use an SSL connection. +SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' + +app = flask.Flask(__name__) +# Note: A secret key is included in the sample so that it works, but if you +# use this code in your application please replace this with a truly secret +# key. See http://flask.pocoo.org/docs/0.12/quickstart/#sessions. +app.secret_key = 'REPLACE ME - this value is here as a placeholder.' + + +@app.route('/') +def index(): + if 'credentials' not in flask.session: + return flask.redirect('authorize') + + # Load the credentials from the session. + credentials = google.oauth2.credentials.Credentials( + **flask.session['credentials']) + + client = googleapiclient.discovery.build( + API_SERVICE_NAME, API_VERSION, credentials=credentials) + + return channels_list_by_username(client, + part='snippet,contentDetails,statistics', + forUsername='GoogleDevelopers') + + +@app.route('/authorize') +def authorize(): + # Create a flow instance to manage the OAuth 2.0 Authorization Grant Flow + # steps. + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES) + flow.redirect_uri = flask.url_for('oauth2callback', _external=True) + authorization_url, state = flow.authorization_url( + # This parameter enables offline access which gives your application + # both an access and refresh token. + access_type='offline', + # This parameter enables incremental auth. + include_granted_scopes='true') + + # Store the state in the session so that the callback can verify that + # the authorization server response. + flask.session['state'] = state + + return flask.redirect(authorization_url) + + +@app.route('/oauth2callback') +def oauth2callback(): + # Specify the state when creating the flow in the callback so that it can + # verify the authorization server response. + state = flask.session['state'] + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) + flow.redirect_uri = flask.url_for('oauth2callback', _external=True) + + # Use the authorization server's response to fetch the OAuth 2.0 tokens. + authorization_response = flask.request.url + flow.fetch_token(authorization_response=authorization_response) + + # Store the credentials in the session. + # ACTION ITEM for developers: + # Store user's access and refresh tokens in your data store if + # incorporating this code into your real app. + credentials = flow.credentials + flask.session['credentials'] = { + 'token': credentials.token, + 'refresh_token': credentials.refresh_token, + 'token_uri': credentials.token_uri, + 'client_id': credentials.client_id, + 'client_secret': credentials.client_secret, + 'scopes': credentials.scopes + } + + return flask.redirect(flask.url_for('index')) + +def channels_list_by_username(client, **kwargs): + response = client.channels().list( + **kwargs + ).execute() + + return flask.jsonify(**response) + + +if __name__ == '__main__': + # When running locally, disable OAuthlib's HTTPs verification. When + # running in production *do not* leave this option enabled. + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' + app.run('localhost', 8090, debug=True) diff --git a/python/retrieve_reports.py b/python/retrieve_reports.py index 7f799c3c..f30005bf 100644 --- a/python/retrieve_reports.py +++ b/python/retrieve_reports.py @@ -1,23 +1,36 @@ #!/usr/bin/python -# Usage example: -# python retrieve_reports.py - -import httplib2 +### +# +# This script retrieves YouTube Reporting API reports. Use cases: +# 1. If you specify a report URL, the script downloads that report. +# 2. Otherwise, if you specify a job ID, the script retrieves a list of +# available reports for that job and prompts you to select a report. +# Then it retrieves that report as in case 1. +# 3. Otherwise, the list retrieves a list of jobs for the user or, +# if specified, the content owner that the user is acting on behalf of. +# Then it prompts the user to select a job, and then executes case 2 and +# then case 1. +# Usage examples: +# python retrieve_reports.py --content_owner_id=<CONTENT_OWNER_ID> --local_file=<LOCAL_FILE> +# python retrieve_reports.py --content_owner_id=<CONTENT_OWNER_ID> --job_id=<JOB_ID> --local_file=<LOCAL_FILE> +# python retrieve_reports.py --content_owner_id=<CONTENT_OWNER_ID> --report_url=<REPORT_URL> --local_file=<LOCAL_FILE> +# +### + +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from apiclient.http import MediaIoBaseDownload +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from googleapiclient.http import MediaIoBaseDownload +from google_auth_oauthlib.flow import InstalledAppFlow from io import FileIO -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains - # the OAuth 2.0 information for this application, including its client_id and # client_secret. You can acquire an OAuth 2.0 client ID and client secret from # the {{ Google Cloud Console }} at @@ -27,84 +40,75 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This OAuth 2.0 access scope allows for read access to the YouTube Analytics monetary reports for -# authenticated user's account. Any request that retrieves earnings or ad performance metrics must -# use this scope. -YOUTUBE_ANALYTICS_MONETARY_READ_SCOPE = ( - "https://www.googleapis.com/auth/yt-analytics-monetary.readonly") -YOUTUBE_REPORTING_API_SERVICE_NAME = "youtubereporting" -YOUTUBE_REPORTING_API_VERSION = "v1" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -# Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_ANALYTICS_MONETARY_READ_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) +CLIENT_SECRETS_FILE = 'client_secret.json' - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_REPORTING_API_SERVICE_NAME, YOUTUBE_REPORTING_API_VERSION, - http=credentials.authorize(httplib2.Http())) +# This OAuth 2.0 access scope allows for read access to YouTube Analytics +# monetary reports for the authenticated user's account. Any request that +# retrieves earnings or ad performance metrics must use this scope. +SCOPES = ['https://www.googleapis.com/auth/yt-analytics-monetary.readonly'] +API_SERVICE_NAME = 'youtubereporting' +API_VERSION = 'v1' +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +# Remove keyword arguments that are not set. +def remove_empty_kwargs(**kwargs): + good_kwargs = {} + if kwargs is not None: + for key, value in kwargs.iteritems(): + if value: + good_kwargs[key] = value + return good_kwargs # Call the YouTube Reporting API's jobs.list method to retrieve reporting jobs. -def list_reporting_jobs(youtube_reporting): - results = youtube_reporting.jobs().list( - ).execute() +def list_reporting_jobs(youtube_reporting, **kwargs): + # Only include the onBehalfOfContentOwner keyword argument if the user + # set a value for the --content_owner argument. + kwargs = remove_empty_kwargs(**kwargs) + + # Retrieve the reporting jobs for the user (or content owner). + results = youtube_reporting.jobs().list(**kwargs).execute() - if "jobs" in results and results["jobs"]: - jobs = results["jobs"] + if 'jobs' in results and results['jobs']: + jobs = results['jobs'] for job in jobs: - print ("Reporting job id: %s\n name: %s\n for reporting type: %s\n" - % (job["id"], job["name"], job["reportTypeId"])) + print ('Reporting job id: %s\n name: %s\n for reporting type: %s\n' + % (job['id'], job['name'], job['reportTypeId'])) else: - print "No jobs found" + print 'No jobs found' return False return True - # Call the YouTube Reporting API's reports.list method to retrieve reports created by a job. -def retrieve_reports(youtube_reporting, job_id): +def retrieve_reports(youtube_reporting, **kwargs): + # Only include the onBehalfOfContentOwner keyword argument if the user + # set a value for the --content_owner argument. + kwargs = remove_empty_kwargs(**kwargs) + + # Retrieve available reports for the selected job. results = youtube_reporting.jobs().reports().list( - jobId=job_id + **kwargs ).execute() - if "reports" in results and results["reports"]: - reports = results["reports"] + if 'reports' in results and results['reports']: + reports = results['reports'] for report in reports: - print ("Report from '%s' to '%s' downloadable at '%s'" - % (report["startTime"], report["endTime"], report["downloadUrl"])) + print ('Report dates: %s to %s\n download URL: %s\n' + % (report['startTime'], report['endTime'], report['downloadUrl'])) # Call the YouTube Reporting API's media.download method to download the report. -def download_report(youtube_reporting, report_url): +def download_report(youtube_reporting, report_url, local_file): request = youtube_reporting.media().download( - resourceName="" + resourceName=' ' ) request.uri = report_url - fh = FileIO('report', mode='wb') + fh = FileIO(local_file, mode='wb') # Stream/download the report in a single request. downloader = MediaIoBaseDownload(fh, request, chunksize=-1) @@ -112,32 +116,57 @@ def download_report(youtube_reporting, report_url): while done is False: status, done = downloader.next_chunk() if status: - print "Download %d%%." % int(status.progress() * 100) - print "Download Complete!" + print 'Download %d%%.' % int(status.progress() * 100) + print 'Download Complete!' -# Prompt the user to enter a job id for report retrieval. Then return the id. +# Prompt the user to select a job and return the specified ID. def get_job_id_from_user(): - job_id = raw_input("Please enter the job id for the report retrieval: ") - print ("You chose '%s' as the job Id for the report retrieval." % job_id) + job_id = raw_input('Please enter the job id for the report retrieval: ') + print ('You chose "%s" as the job Id for the report retrieval.' % job_id) return job_id - -# Prompt the user to enter a report URL for download. Then return the URL. +# Prompt the user to select a report URL and return the specified URL. def get_report_url_from_user(): - report_url = raw_input("Please enter the report URL to download: ") - print ("You chose '%s' to download." % report_url) + report_url = raw_input('Please enter the report URL to download: ') + print ('You chose "%s" to download.' % report_url) return report_url -if __name__ == "__main__": - args = argparser.parse_args() - - youtube_reporting = get_authenticated_service(args) +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--content_owner', default='', + help='ID of content owner for which you are retrieving jobs and reports') + parser.add_argument('--job_id', default=None, + help='ID of the job for which you are retrieving reports. If not ' + + 'provided AND report_url is also not provided, then the script ' + + 'calls jobs.list() to retrieve a list of jobs.') + parser.add_argument('--report_url', default=None, + help='URL of the report to retrieve. If not specified, the script ' + + 'calls reports.list() to retrieve a list of reports for the ' + + 'selected job.') + parser.add_argument('--local_file', default='yt_report.txt', + help='The name of the local file where the downloaded report will be written.') + args = parser.parse_args() + + youtube_reporting = get_authenticated_service() try: - if list_reporting_jobs(youtube_reporting): - retrieve_reports(youtube_reporting, get_job_id_from_user()) - download_report(youtube_reporting, get_report_url_from_user()) + # If the user has not specified a job ID or report URL, retrieve a list + # of available jobs and prompt the user to select one. + if not args.job_id and not args.report_url: + if list_reporting_jobs(youtube_reporting, + onBehalfOfContentOwner=args.content_owner): + args.job_id = get_job_id_from_user() + + # If the user has not specified a report URL, retrieve a list of reports + # available for the specified job and prompt the user to select one. + if args.job_id and not args.report_url: + retrieve_reports(youtube_reporting, + jobId=args.job_id, + onBehalfOfContentOwner=args.content_owner) + args.report_url = get_report_url_from_user() + + # Download the selected report. + if args.report_url: + download_report(youtube_reporting, args.report_url, args.local_file) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - else: - print "Retrieved reports." + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/search.py b/python/search.py index 8e9d2add..ccb17729 100644 --- a/python/search.py +++ b/python/search.py @@ -1,17 +1,25 @@ #!/usr/bin/python -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.tools import argparser +# This sample executes a search request for the specified search term. +# Sample usage: +# python search.py --q=surfing --max-results=10 +# NOTE: To use the sample, you must provide a developer key obtained +# in the Google APIs Console. Search for "REPLACE_ME" in this code +# to find the correct place to provide that key.. + +import argparse + +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError # Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps # tab of # https://cloud.google.com/console # Please ensure that you have enabled the YouTube Data API for your project. -DEVELOPER_KEY = "REPLACE_ME" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" +DEVELOPER_KEY = 'REPLACE_ME' +YOUTUBE_API_SERVICE_NAME = 'youtube' +YOUTUBE_API_VERSION = 'v3' def youtube_search(options): youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, @@ -21,7 +29,7 @@ def youtube_search(options): # query term. search_response = youtube.search().list( q=options.q, - part="id,snippet", + part='id,snippet', maxResults=options.max_results ).execute() @@ -31,28 +39,29 @@ def youtube_search(options): # Add each result to the appropriate list, and then display the lists of # matching videos, channels, and playlists. - for search_result in search_response.get("items", []): - if search_result["id"]["kind"] == "youtube#video": - videos.append("%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["videoId"])) - elif search_result["id"]["kind"] == "youtube#channel": - channels.append("%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["channelId"])) - elif search_result["id"]["kind"] == "youtube#playlist": - playlists.append("%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["playlistId"])) - - print "Videos:\n", "\n".join(videos), "\n" - print "Channels:\n", "\n".join(channels), "\n" - print "Playlists:\n", "\n".join(playlists), "\n" - - -if __name__ == "__main__": - argparser.add_argument("--q", help="Search term", default="Google") - argparser.add_argument("--max-results", help="Max results", default=25) - args = argparser.parse_args() + for search_result in search_response.get('items', []): + if search_result['id']['kind'] == 'youtube#video': + videos.append('%s (%s)' % (search_result['snippet']['title'], + search_result['id']['videoId'])) + elif search_result['id']['kind'] == 'youtube#channel': + channels.append('%s (%s)' % (search_result['snippet']['title'], + search_result['id']['channelId'])) + elif search_result['id']['kind'] == 'youtube#playlist': + playlists.append('%s (%s)' % (search_result['snippet']['title'], + search_result['id']['playlistId'])) + + print 'Videos:\n', '\n'.join(videos), '\n' + print 'Channels:\n', '\n'.join(channels), '\n' + print 'Playlists:\n', '\n'.join(playlists), '\n' + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--q', help='Search term', default='Google') + parser.add_argument('--max-results', help='Max results', default=25) + args = parser.parse_args() try: youtube_search(args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/shuffle_channel_sections.py b/python/shuffle_channel_sections.py index 2d9e0e29..fb05e522 100644 --- a/python/shuffle_channel_sections.py +++ b/python/shuffle_channel_sections.py @@ -1,15 +1,13 @@ #!/usr/bin/python -import httplib2 import os import random -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -23,52 +21,27 @@ # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) def get_current_channel_sections(youtube): channel_sections_list_response = youtube.channelSections().list( - part="snippet,contentDetails", + part='snippet,contentDetails', mine=True ).execute() - return channel_sections_list_response["items"] + return channel_sections_list_response['items'] def shuffle_channel_sections(youtube, channel_sections): # This will randomly reorder the items in the channel_sections list. @@ -77,21 +50,19 @@ def shuffle_channel_sections(youtube, channel_sections): for channel_section in channel_sections: # Each section in the list of shuffled sections is sequentially # set to position 0, i.e. the top. - channel_section["snippet"]["position"] = 0 + channel_section['snippet']['position'] = 0 youtube.channelSections().update( - part="snippet,contentDetails", + part='snippet,contentDetails', body=channel_section ).execute() if __name__ == '__main__': - args = argparser.parse_args() - - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: channel_sections = get_current_channel_sections(youtube) shuffle_channel_sections(youtube, channel_sections) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "The existing channel sections have been randomly shuffled." + print 'The existing channel sections have been randomly shuffled.' diff --git a/python/topics.py b/python/topics.py deleted file mode 100644 index 5bbfbfd7..00000000 --- a/python/topics.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/python - -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.tools import argparser - -import json -import urllib - - -# Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps -# tab of -# https://cloud.google.com/console -# Please ensure that you have enabled the YouTube Data API for your project. -DEVELOPER_KEY = "REPLACE_ME" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" -FREEBASE_SEARCH_URL = "https://www.googleapis.com/freebase/v1/search?%s" - -def get_topic_id(options): - # Retrieve a list of Freebase topics associated with the provided query term. - freebase_params = dict(query=options.query, key=DEVELOPER_KEY) - freebase_url = FREEBASE_SEARCH_URL % urllib.urlencode(freebase_params) - freebase_response = json.loads(urllib.urlopen(freebase_url).read()) - - if len(freebase_response["result"]) == 0: - exit("No matching terms were found in Freebase.") - - # Display the list of matching Freebase topics. - mids = [] - index = 1 - print "The following topics were found:" - for result in freebase_response["result"]: - mids.append(result["mid"]) - print " %2d. %s (%s)" % (index, result.get("name", "Unknown"), - result.get("notable", {}).get("name", "Unknown")) - index += 1 - - # Display a prompt for the user to select a topic and return the topic ID - # of the selected topic. - mid = None - while mid is None: - index = raw_input("Enter a topic number to find related YouTube %ss: " % - options.type) - try: - mid = mids[int(index) - 1] - except ValueError: - pass - return mid - - -def youtube_search(mid, options): - youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - developerKey=DEVELOPER_KEY) - - # Call the search.list method to retrieve results associated with the - # specified Freebase topic. - search_response = youtube.search().list( - topicId=mid, - type=options.type, - part="id,snippet", - maxResults=options.max_results - ).execute() - - # Print the title and ID of each matching resource. - for search_result in search_response.get("items", []): - if search_result["id"]["kind"] == "youtube#video": - print "%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["videoId"]) - elif search_result["id"]["kind"] == "youtube#channel": - print "%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["channelId"]) - elif search_result["id"]["kind"] == "youtube#playlist": - print "%s (%s)" % (search_result["snippet"]["title"], - search_result["id"]["playlistId"]) - - -if __name__ == "__main__": - argparser.add_argument("--query", help="Freebase search term", default="Google") - argparser.add_argument("--max-results", help="Max YouTube results", - default=25) - argparser.add_argument("--type", - help="YouTube result type: video, playlist, or channel", default="channel") - args = argparser.parse_args() - - mid = get_topic_id(args) - try: - youtube_search(mid, args) - except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) diff --git a/python/update_video.py b/python/update_video.py index 2d428e6b..58609cb1 100644 --- a/python/update_video.py +++ b/python/update_video.py @@ -1,14 +1,16 @@ #!/usr/bin/python -import httplib2 +# Update the snippet metadata for a video. Sample usage: +# python update_video.py --video_id=<VIDEO_ID> --tags="<TAG1, TAG2>" --title="New title" --description="New description" + +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -21,89 +23,87 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) - -def update_video(youtube, options): +def update_video(youtube, args): # Call the API's videos.list method to retrieve the video resource. videos_list_response = youtube.videos().list( - id=options.video_id, + id=args.video_id, part='snippet' ).execute() - # If the response does not contain an array of "items" then the video was + # If the response does not contain an array of 'items' then the video was # not found. - if not videos_list_response["items"]: - print "Video '%s' was not found." % options.video_id + if not videos_list_response['items']: + print 'Video "%s" was not found.' % args.video_id sys.exit(1) # Since the request specified a video ID, the response only contains one # video resource. This code extracts the snippet from that resource. - videos_list_snippet = videos_list_response["items"][0]["snippet"] + videos_list_snippet = videos_list_response['items'][0]['snippet'] + + # Set video title, description, default language if specified in args. + if args.title: + videos_list_snippet['title'] = args.title + if args.description: + videos_list_snippet['description'] = args.description # Preserve any tags already associated with the video. If the video does # not have any tags, create a new array. Append the provided tag to the # list of tags associated with the video. - if "tags" not in videos_list_snippet: - videos_list_snippet["tags"] = [] - videos_list_snippet["tags"].append(options.tag) + if 'tags' not in videos_list_snippet: + videos_list_snippet['tags'] = [] + if args.tags: + videos_list_snippet['tags'] = args.tags.split(',') + elif args.add_tag: + videos_list_snippet['tags'].append(args.add_tag) + + print(videos_list_snippet); # Update the video resource by calling the videos.update() method. videos_update_response = youtube.videos().update( part='snippet', body=dict( snippet=videos_list_snippet, - id=options.video_id + id=args.video_id )).execute() -if __name__ == "__main__": - argparser.add_argument("--video-id", help="ID of video to update.", + print('The updated video metadata is:\n' + + 'Title: ' + videos_update_response['snippet']['title'] + '\n') + if videos_update_response['snippet']['description']: + print ('Description: ' + + videos_update_response['snippet']['description'] + '\n') + if videos_update_response['snippet']['tags']: + print ('Tags: ' + ','.join(videos_update_response['snippet']['tags']) + '\n') + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--video_id', help='ID of video to update.', required=True) - argparser.add_argument("--tag", default="youtube", - help="Additional tag to add to video.") - args = argparser.parse_args() - - youtube = get_authenticated_service(args) + parser.add_argument('--tags', + help='Comma-separated list of tags relevant to the video. This argument ' + + 'replaces the existing list of tags.') + parser.add_argument('--add_tag', help='Additional tag to add to video. ' + + 'This argument does not affect current tags.') + parser.add_argument('--title', help='Title of the video.') + parser.add_argument('--description', help='Description of the video.') + args = parser.parse_args() + + youtube = get_authenticated_service() try: update_video(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) - else: - print "Tag '%s' was added to video id '%s'." % (args.tag, args.video_id) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) + print 'Tag "%s" was added to video id "%s".' % (args.add_tag, args.video_id) diff --git a/python/upload_video.py b/python/upload_video.py index 2e68766c..0b52bc36 100644 --- a/python/upload_video.py +++ b/python/upload_video.py @@ -1,18 +1,18 @@ #!/usr/bin/python +import argparse import httplib import httplib2 import os import random -import sys import time -from apiclient.discovery import build -from apiclient.errors import HttpError -from apiclient.http import MediaFileUpload -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from googleapiclient.http import MediaFileUpload +from google_auth_oauthlib.flow import InstalledAppFlow # Explicitly tell the underlying HTTP transport library not to retry, since @@ -42,53 +42,27 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows an application to upload files to the # authenticated user's YouTube channel, but doesn't allow other types of access. -YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" +SCOPES = ['https://www.googleapis.com/auth/youtube.upload'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 +VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') -To make this sample run you will need to populate the client_secrets.json file -found at: - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - -VALID_PRIVACY_STATUSES = ("public", "private", "unlisted") - - -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=YOUTUBE_UPLOAD_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) def initialize_upload(youtube, options): tags = None if options.keywords: - tags = options.keywords.split(",") + tags = options.keywords.split(',') body=dict( snippet=dict( @@ -104,14 +78,14 @@ def initialize_upload(youtube, options): # Call the API's videos.insert method to create and upload the video. insert_request = youtube.videos().insert( - part=",".join(body.keys()), + part=','.join(body.keys()), body=body, # The chunksize parameter specifies the size of each chunk of data, in # bytes, that will be uploaded at a time. Set a higher value for # reliable connections as fewer chunks lead to faster uploads. Set a lower # value for better recovery on less reliable connections. # - # Setting "chunksize" equal to -1 in the code below means that the entire + # Setting 'chunksize' equal to -1 in the code below means that the entire # file will be uploaded in a single HTTP request. (If the upload fails, # it will still be retried where it left off.) This is usually a best # practice, but if you're using Python older than 2.6 or if you're @@ -124,58 +98,57 @@ def initialize_upload(youtube, options): # This method implements an exponential backoff strategy to resume a # failed upload. -def resumable_upload(insert_request): +def resumable_upload(request): response = None error = None retry = 0 while response is None: try: - print "Uploading file..." - status, response = insert_request.next_chunk() + print 'Uploading file...' + status, response = request.next_chunk() if response is not None: if 'id' in response: - print "Video id '%s' was successfully uploaded." % response['id'] + print 'Video id "%s" was successfully uploaded.' % response['id'] else: - exit("The upload failed with an unexpected response: %s" % response) + exit('The upload failed with an unexpected response: %s' % response) except HttpError, e: if e.resp.status in RETRIABLE_STATUS_CODES: - error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, + error = 'A retriable HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: raise except RETRIABLE_EXCEPTIONS, e: - error = "A retriable error occurred: %s" % e + error = 'A retriable error occurred: %s' % e if error is not None: print error retry += 1 if retry > MAX_RETRIES: - exit("No longer attempting to retry.") + exit('No longer attempting to retry.') max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep - print "Sleeping %f seconds and then retrying..." % sleep_seconds + print 'Sleeping %f seconds and then retrying...' % sleep_seconds time.sleep(sleep_seconds) if __name__ == '__main__': - argparser.add_argument("--file", required=True, help="Video file to upload") - argparser.add_argument("--title", help="Video title", default="Test Title") - argparser.add_argument("--description", help="Video description", - default="Test Description") - argparser.add_argument("--category", default="22", - help="Numeric video category. " + - "See https://developers.google.com/youtube/v3/docs/videoCategories/list") - argparser.add_argument("--keywords", help="Video keywords, comma separated", - default="") - argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES, - default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.") - args = argparser.parse_args() - - if not os.path.exists(args.file): - exit("Please specify a valid file using the --file= parameter.") - - youtube = get_authenticated_service(args) + parser = argparse.ArgumentParser() + parser.add_argument('--file', required=True, help='Video file to upload') + parser.add_argument('--title', help='Video title', default='Test Title') + parser.add_argument('--description', help='Video description', + default='Test Description') + parser.add_argument('--category', default='22', + help='Numeric video category. ' + + 'See https://developers.google.com/youtube/v3/docs/videoCategories/list') + parser.add_argument('--keywords', help='Video keywords, comma separated', + default='') + parser.add_argument('--privacyStatus', choices=VALID_PRIVACY_STATUSES, + default='private', help='Video privacy status.') + args = parser.parse_args() + + youtube = get_authenticated_service() + try: initialize_upload(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/video_localizations.py b/python/video_localizations.py index bd08653e..20778c8e 100644 --- a/python/video_localizations.py +++ b/python/video_localizations.py @@ -3,15 +3,15 @@ # Usage example: # python video_localizations.py --action='<action>' --video_id='<video_id>' --default_language='<default_language>' --language='<language>' --title='<title>' --description='<description>' -import httplib2 +import argparse import os -import sys +import re -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -25,142 +25,155 @@ # https://developers.google.com/youtube/v3/guides/authentication # For more information about the client_secrets.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account. -YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - %s -with information from the APIs Console -https://console.developers.google.com - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) +SCOPES = ['https://www.googleapis.com/auth/youtube'] +API_SERVICE_NAME = 'youtube' +API_VERSION = 'v3' -# Authorize the request and store authorization credentials. -def get_authenticated_service(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_WRITE_SCOPE, - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=credentials.authorize(httplib2.Http())) +# Supported actions +ACTIONS = ('get', 'list', 'set') +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) # Call the API's videos.update method to update an existing video's default language, # localized title and description in a specific language. -def set_video_localization(youtube, video_id, default_language, language, title, description): +def set_video_localization(youtube, args): + + # Retrieve the snippet and localizations for the video. results = youtube.videos().list( - part="snippet,localizations", - id=video_id + part='snippet,localizations', + id=args.video_id ).execute() - video = results["items"][0] - # Ensure that a value is set for the resource's snippet.defaultLanguage property. - video["snippet"]["defaultLanguage"] = default_language - if "localizations" not in video: - video["localizations"] = {} - video["localizations"][language] = { - "title": title, - "description": description - } - + video = results['items'][0] + + # If the language argument is set, set the localized title and description + # for that language. The "title" and "description" arguments have default + # values to make the script simpler to run as a demo. In an actual app, you + # would likely want to set those arguments also. + if args.language and args.language != '': + if 'localizations' not in video: + video['localizations'] = {} + + video['localizations'][args.language] = { + 'title': args.title, + 'description': args.description + } + + # If the default language is set AND there is localized metadata for that + # language, set the video's title and description to match the localized + # title and description for the newly set default language. + if args.default_language and args.default_language in video['localizations']: + video['snippet']['defaultLanguage'] = args.default_language + video['snippet']['title'] = ( + video['localizations'][args.default_language]['title']) + video['snippet']['description'] = ( + video['localizations'][args.default_language]['description']) + + # Update the video resource. update_result = youtube.videos().update( - part="snippet,localizations", - body=video - ).execute() - - localization = update_result["localizations"][language] - - print ("Updated video '%s' default language to '%s', localized title to '%s'" - " and description to '%s' in language '%s'" - % (video_id, default_language, localization["title"], localization["description"], language)) - + part='snippet,localizations', + body=video + ).execute() + + # Print the actions taken by running the script. + if args.language: + for language in update_result['localizations']: + # Languages with locales, like "pt-br" are returned as pt-BR in metadata. + # This ensures that the language specified when running the script can be + # matched to the language returned in the metadata. + if language.lower() == args.language.lower(): + localization = update_result['localizations'][args.language] + print ('Updated video \'%s\' localized title to \'%s\'' + ' and description to \'%s\' in language \'%s\'' % + (args.video_id, + localization['title'], + localization['description'], + args.language)) + break + + if (args.default_language and + args.default_language == update_result['snippet']['defaultLanguage']): + print 'Updated default language to %s' % args.default_language # Call the API's videos.list method to retrieve an existing video localization. # If the localized text is not available in the requested language, -# this method will return text in the default language. -def get_video_localization(youtube, video_id, language): +# this method returns text in the default language. +def get_video_localization(youtube, args): results = youtube.videos().list( - part="snippet", - id=video_id, - hl=language + part='snippet', + id=args.video_id, + hl=args.language ).execute() # The localized object contains localized text if the hl parameter specified # a language for which localized text is available. Otherwise, the localized - # object will contain metadata in the default language. - localized = results["items"][0]["snippet"]["localized"] + # object contains metadata in the default language. + localized = results['items'][0]['snippet']['localized'] - print ("Video title is '%s' and description is '%s' in language '%s'" - % (localized["title"], localized["description"], language)) + print ('Video title is \'%s\' and description is \'%s\' in language \'%s\'' + % (localized['title'], localized['description'], args.language)) # Call the API's videos.list method to list the existing video localizations. -def list_video_localizations(youtube, video_id): +def list_video_localizations(youtube, args): results = youtube.videos().list( - part="snippet,localizations", - id=video_id + part='snippet,localizations', + id=args.video_id ).execute() - localizations = results["items"][0]["localizations"] - - for language, localization in localizations.iteritems(): - print ("Video title is '%s' and description is '%s' in language '%s'" - % (localization["title"], localization["description"], language)) - - -if __name__ == "__main__": - # The "action" option specifies the action to be processed. - argparser.add_argument("--action", help="Action") - # The "video_id" option specifies the ID of the selected YouTube video. - argparser.add_argument("--video_id", - help="ID for video for which the localization will be applied.") - # The "default_language" option specifies the language of the video's default metadata. - argparser.add_argument("--default_language", help="Default language of the video to update.", - default="en") - # The "language" option specifies the language of the localization that is being processed. - argparser.add_argument("--language", help="Language of the localization.", default="de") - # The "title" option specifies the localized title of the video to be set. - argparser.add_argument("--title", help="Localized title of the video to be set.", - default="Localized Title") - # The "description" option specifies the localized description of the video to be set. - argparser.add_argument("--description", help="Localized description of the video to be set.", - default="Localized Description") - - args = argparser.parse_args() + if 'localizations' in results['items'][0]: + localizations = results['items'][0]['localizations'] + + for language, localization in localizations.iteritems(): + print ('Video title is \'%s\' and description is \'%s\' in language \'%s\'' + % (localization['title'], localization['description'], language)) + else: + print 'There aren\'t any localizations for this video yet.' + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # The action to be processed: 'get', 'list', and 'set' are supported. + parser.add_argument('--action', help='Action', choices=ACTIONS, required=True) + # The ID of the selected YouTube video. + parser.add_argument('--video_id', + help='The video ID for which localizations are being set or retrieved.', + required=True) + # The language of the video's default metadata. + parser.add_argument('--default_language', + help='Default language of the video to update.') + # The language of the localization that is being processed. + parser.add_argument('--language', help='Language of the localization.') + # The localized video title for the specified language. + parser.add_argument('--title', + help='Localized title of the video to be set.', + default='Localized Title') + # The localized description for the specified language. + parser.add_argument('--description', + help='Localized description of the video to be set.', + default='Localized Description') + + args = parser.parse_args() if not args.video_id: - exit("Please specify video id using the --video_id= parameter.") + exit('Please specify video id using the --video_id= parameter.') - youtube = get_authenticated_service(args) + youtube = get_authenticated_service() try: if args.action == 'set': - set_video_localization(youtube, args.video_id, args.default_language, args.language, args.title, args.description) + set_video_localization(youtube, args) elif args.action == 'get': - get_video_localization(youtube, args.video_id, args.language) + get_video_localization(youtube, args) elif args.action == 'list': - list_video_localizations(youtube, args.video_id) - else: - exit("Please specify a valid action using the --action= parameter.") + list_video_localizations(youtube, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) else: - print "Set and retrieved localized metadata for a video." + print 'Set and retrieved localized metadata for a video diff --git a/python/yt_analytics_report.py b/python/yt_analytics_report.py index 6e816106..35a52f31 100644 --- a/python/yt_analytics_report.py +++ b/python/yt_analytics_report.py @@ -1,15 +1,41 @@ #!/usr/bin/python -from datetime import datetime, timedelta -import httplib2 +### +# +# Retrieves YouTube Analytics report data. The script's default behavior +# is to retrieve data for the authenticated user's channel. However, if you +# set a value for the --content-owner command-line argument, then the script +# retrieves data for that content owner. Note that the user running the script +# must be authorized to retrieve data for that content owner. +# +# Note that when you retrieve Analytics data for a content owner, your API +# request must set a value for the "filters" request parameter as explained +# in the API documentation here: +# https://developers.google.com/youtube/analytics/v1/content_owner_reports#Filters +# +# By default, if you set a value for the --content-owner argument, then the +# "filters" parameter is set to "claimedStatus==claimed". (On the other hand, +# this value is not set if you are retrieving data for your own channel.) +# +# You can use the --filters command-line argument to set the "filters" parameter +# for any request. For example: +# * --filters="channel==CHANNEL_ID" +# * --filters="channel==CHANNEL_ID;country==COUNTRY_CODE" +# * --filters="video==VIDEO_ID" +# * --filters="claimedStatus==claimed;uploaderType==thirdParty" +# * etc. +# +### + +import argparse import os -import sys -from apiclient.discovery import build -from apiclient.errors import HttpError -from oauth2client.client import flow_from_clientsecrets -from oauth2client.file import Storage -from oauth2client.tools import argparser, run_flow +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow +from datetime import datetime, timedelta # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains @@ -17,115 +43,128 @@ # client_secret. You can acquire an OAuth 2.0 client ID and client secret from # the {{ Google Cloud Console }} at # {{ https://cloud.google.com/console }}. -# Please ensure that you have enabled the YouTube Data and YouTube Analytics -# APIs for your project. -# For more information about using OAuth2 to access the YouTube Data API, see: -# https://developers.google.com/youtube/v3/guides/authentication -# For more information about the client_secrets.json file format, see: +# Please ensure that you have enabled the YouTube Analytics API for your project. +# For more information about using OAuth2 to access the YouTube Analytics API, see: +# https://developers.google.com/youtube/reporting/guides/authorization +# For more information about the client_secret.json file format, see: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -CLIENT_SECRETS_FILE = "client_secrets.json" +CLIENT_SECRETS_FILE = 'client_secret.json' # These OAuth 2.0 access scopes allow for read-only access to the authenticated # user's account for both YouTube Data API resources and YouTube Analytics Data. -YOUTUBE_SCOPES = ["https://www.googleapis.com/auth/youtube.readonly", - "https://www.googleapis.com/auth/yt-analytics.readonly"] -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" -YOUTUBE_ANALYTICS_API_SERVICE_NAME = "youtubeAnalytics" -YOUTUBE_ANALYTICS_API_VERSION = "v1" - -# This variable defines a message to display if the CLIENT_SECRETS_FILE is -# missing. -MISSING_CLIENT_SECRETS_MESSAGE = """ -WARNING: Please configure OAuth 2.0 - -To make this sample run you will need to populate the client_secrets.json file -found at: - - %s - -with information from the {{ Cloud Console }} -{{ https://cloud.google.com/console }} - -For more information about the client_secrets.json file format, please visit: -https://developers.google.com/api-client-library/python/guide/aaa_client_secrets -""" % os.path.abspath(os.path.join(os.path.dirname(__file__), - CLIENT_SECRETS_FILE)) - - -def get_authenticated_services(args): - flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, - scope=" ".join(YOUTUBE_SCOPES), - message=MISSING_CLIENT_SECRETS_MESSAGE) - - storage = Storage("%s-oauth2.json" % sys.argv[0]) - credentials = storage.get() - - if credentials is None or credentials.invalid: - credentials = run_flow(flow, storage, args) - - http = credentials.authorize(httplib2.Http()) - - youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, - http=http) - youtube_analytics = build(YOUTUBE_ANALYTICS_API_SERVICE_NAME, - YOUTUBE_ANALYTICS_API_VERSION, http=http) - - return (youtube, youtube_analytics) - -def get_channel_id(youtube): - channels_list_response = youtube.channels().list( - mine=True, - part="id" - ).execute() - - return channels_list_response["items"][0]["id"] - -def run_analytics_report(youtube_analytics, channel_id, options): - # Call the Analytics API to retrieve a report. For a list of available - # reports, see: +SCOPES = ['https://www.googleapis.com/auth/yt-analytics.readonly'] +API_SERVICE_NAME = 'youtubeAnalytics' +API_VERSION = 'v1' + +# Authorize the request and store authorization credentials. +def get_authenticated_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + + api_service = build(API_SERVICE_NAME, API_VERSION, credentials=credentials) + return api_service + +# Remove keyword arguments that are not set. +def remove_empty_args(args): + original_args = vars(args) + good_args = {} + if original_args is not None: + for key, value in original_args.iteritems(): + # The channel_id and content_owner arguments are provided as a means + # of properly setting the "ids" parameter value. However, they should + # not be included in the API request (since they're not parameters + # supported by this method). + if value and key != 'channel_id' and key != 'content_owner': + good_args[key] = value + return good_args + +# Set the "ids" request parameter for the YouTube Analytics API request. +def set_ids_parameter(args): + if args.content_owner: + args.ids = 'contentOwner==' + args.content_owner + if args.filters == '': + args.filters = 'claimedStatus==claimed' + elif args.channel_id: + args.ids = 'channel==' + args.channel_id + else: + args.ids = 'channel==MINE' + args = remove_empty_args(args) + print args + return args + +def run_analytics_report(youtube_analytics, args): + # Call the Analytics API to retrieve a report. Pass args in as keyword + # arguments to set values for the following parameters: + # + # * ids + # * metrics + # * dimensions + # * filters + # * start_date + # * end_date + # * sort + # * max_results + # + # For a list of available reports, see: # https://developers.google.com/youtube/analytics/v1/channel_reports + # https://developers.google.com/youtube/analytics/v1/content_owner_reports analytics_query_response = youtube_analytics.reports().query( - ids="channel==%s" % channel_id, - metrics=options.metrics, - dimensions=options.dimensions, - start_date=options.start_date, - end_date=options.end_date, - max_results=options.max_results, - sort=options.sort + **args ).execute() - print "Analytics Data for Channel %s" % channel_id + #print 'Analytics Data for Channel %s' % channel_id - for column_header in analytics_query_response.get("columnHeaders", []): - print "%-20s" % column_header["name"], + for column_header in analytics_query_response.get('columnHeaders', []): + print '%-20s' % column_header['name'], print - for row in analytics_query_response.get("rows", []): + for row in analytics_query_response.get('rows', []): for value in row: - print "%-20s" % value, + print '%-20s' % value, print -if __name__ == "__main__": +if __name__ == '__main__': now = datetime.now() - one_day_ago = (now - timedelta(days=1)).strftime("%Y-%m-%d") - one_week_ago = (now - timedelta(days=7)).strftime("%Y-%m-%d") - - argparser.add_argument("--metrics", help="Report metrics", - default="views,comments,favoritesAdded,favoritesRemoved,likes,dislikes,shares") - argparser.add_argument("--dimensions", help="Report dimensions", - default="video") - argparser.add_argument("--start-date", default=one_week_ago, - help="Start date, in YYYY-MM-DD format") - argparser.add_argument("--end-date", default=one_day_ago, - help="End date, in YYYY-MM-DD format") - argparser.add_argument("--max-results", help="Max results", default=10) - argparser.add_argument("--sort", help="Sort order", default="-views") - args = argparser.parse_args() - - (youtube, youtube_analytics) = get_authenticated_services(args) + one_day_ago = (now - timedelta(days=1)).strftime('%Y-%m-%d') + one_week_ago = (now - timedelta(days=7)).strftime('%Y-%m-%d') + + parser = argparse.ArgumentParser() + + # Set channel ID or content owner. Default is authenticated user's channel. + parser.add_argument('--channel-id', default='', + help='YouTube channel ID for which data should be retrieved. ' + + 'Note that the default behavior is to retrieve data for ' + + 'the authenticated user\'s channel.') + parser.add_argument('--content-owner', default='', + help='The name of the content owner for which data should be ' + + 'retrieved. If you retrieve data for a content owner, then ' + + 'your API request must also set a value for the "filters" ' + + 'parameter. See the help for that parameter for more details.') + + # Metrics, dimensions, filters + parser.add_argument('--metrics', help='Report metrics', + default='views,estimatedMinutesWatched,averageViewDuration') + parser.add_argument('--dimensions', help='Report dimensions', default='') + parser.add_argument('--filters', default='', + help='Filters for the report. Note that the filters request parameter ' + + 'must be set in YouTube Analytics API requests for content owner ' + + 'reports. The script sets "filters=claimedStatus==claimed" if a ' + + 'content owner is specified and filters are not specified.') + + # Report dates. Defaults to start 7 days ago, end yesterday. + parser.add_argument('--start-date', default=one_week_ago, + help='Start date, in YYYY-MM-DD format') + parser.add_argument('--end-date', default=one_day_ago, + help='End date, in YYYY-MM-DD format') + + parser.add_argument('--max-results', help='Max results') + parser.add_argument('--sort', help='Sort order', default='') + + args = parser.parse_args() + args = set_ids_parameter(args) + + youtube_analytics = get_authenticated_service() try: - channel_id = get_channel_id(youtube) - run_analytics_report(youtube_analytics, channel_id, args) + run_analytics_report(youtube_analytics, args) except HttpError, e: - print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content) + print 'An HTTP error %d occurred:\n%s' % (e.resp.status, e.content) diff --git a/python/yt_analytics_v2.py b/python/yt_analytics_v2.py new file mode 100644 index 00000000..47aa32e2 --- /dev/null +++ b/python/yt_analytics_v2.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +import os +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from google_auth_oauthlib.flow import InstalledAppFlow + +SCOPES = ['https://www.googleapis.com/auth/yt-analytics.readonly'] + +API_SERVICE_NAME = 'youtubeAnalytics' +API_VERSION = 'v2' +CLIENT_SECRETS_FILE = 'YOUR_CLIENT_SECRET_FILE.json' +def get_service(): + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) + credentials = flow.run_console() + return build(API_SERVICE_NAME, API_VERSION, credentials = credentials) + +def execute_api_request(client_library_function, **kwargs): + response = client_library_function( + **kwargs + ).execute() + + print(response) + +if __name__ == '__main__': + # Disable OAuthlib's HTTPs verification when running locally. + # *DO NOT* leave this option enabled when running in production. + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' + + youtubeAnalytics = get_service() + execute_api_request( + youtubeAnalytics.reports().query, + ids='channel==MINE', + startDate='2017-01-01', + endDate='2017-12-31', + metrics='estimatedMinutesWatched,views,likes,subscribersGained' + dimensions='day', + sort='day' + ) diff --git a/python_appengine/README.md b/python_appengine/README.md new file mode 100644 index 00000000..d380961b --- /dev/null +++ b/python_appengine/README.md @@ -0,0 +1,14 @@ +## Samples in this directory: + +### [Retrieve a channel's uploads](/python_appengine/user_uploads/main.py) + +Method: youtube.playlistItems.list +Description: This code sample calls the API's <code>playlistItems.list</code> method to retrieve a list of videos uploaded +to a specified channel. The channel can be identified by its channel ID or channel name. The code also calls the +<code>channels.list</code> method to retrieve the playlist ID that identifies the channel's uploaded videos. + +### [Search by keyword](/python_appengine/search/main.py) + +Method: youtube.search.list +Description: This code sample calls the API's <code>search.list</code> method to retrieve search results associated +with a particular keyword. diff --git a/python_appengine/search/main.py b/python_appengine/search/main.py index d5640d61..baefaab6 100644 --- a/python_appengine/search/main.py +++ b/python_appengine/search/main.py @@ -23,7 +23,7 @@ def get(self): if DEVELOPER_KEY == "REPLACE_ME": self.response.write("""You must set up a project and get an API key to run this project. Please visit - <landing page> to do so.""" + <landing page> to do so.""") else: youtube = build( YOUTUBE_API_SERVICE_NAME, diff --git a/python_appengine/topics/app.yaml b/python_appengine/topics/app.yaml deleted file mode 100644 index 6ccfdcc6..00000000 --- a/python_appengine/topics/app.yaml +++ /dev/null @@ -1,23 +0,0 @@ -application: your-app-id -version: 1 -runtime: python27 -api_version: 1 -threadsafe: yes - -handlers: -- url: /favicon\.ico - static_files: favicon.ico - upload: favicon\.ico - -- url: /favicon\.ico - static_files: favicon.ico - upload: favicon\.ico - -- url: /.* - script: main.app - -libraries: -- name: webapp2 - version: "2.5.2" -- name: jinja2 - version: latest diff --git a/python_appengine/topics/index.html b/python_appengine/topics/index.html deleted file mode 100644 index 62be2374..00000000 --- a/python_appengine/topics/index.html +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE html> -<html> - <body> - <p>Videos: </p> - {% for video in videos %} - <img src="{{ video.snippet.thumbnails.default.url }}" style="padding-right: 15px"> - <div style="vertical-align:top; display: inline-block"> - <strong><a href="https://www.youtube.com/watch?v={{ video.id.videoId }}">{{ video.snippet.title }}</a></strong> - </div><br/><br/> - {% endfor %} - </body> -</html> diff --git a/python_appengine/topics/main.py b/python_appengine/topics/main.py deleted file mode 100644 index e389c4cd..00000000 --- a/python_appengine/topics/main.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import urllib -import webapp2 -import jinja2 - -from apiclient.discovery import build -from optparse import OptionParser - -import json - -JINJA_ENVIRONMENT = jinja2.Environment( - loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), - extensions=['jinja2.ext.autoescape']) - -REGISTRATION_INSTRUCTIONS = """ - You must set up a project and get an API key to run this code. Please see - the instructions for creating a project and a key at <a - href="https://developers.google.com/youtube/registering_an_application" - >https://developers.google.com/youtube/registering_an_application</a>. - <br><br> - Make sure that you have enabled the YouTube Data API (v3) and the Freebase - API for your project.""" - -# Set API_KEY to the "API key" value from the Google Developers Console: -# https://console.developers.google.com/project/_/apiui/credential -# Please ensure that you have enabled the YouTube Data API and Freebase API -# for your project. -API_KEY = "REPLACE_ME" -YOUTUBE_API_SERVICE_NAME = "youtube" -YOUTUBE_API_VERSION = "v3" -FREEBASE_SEARCH_URL = "https://www.googleapis.com/freebase/v1/search?%s" -QUERY_TERM = "dog" - -class MainHandler(webapp2.RequestHandler): - - def get(self): - if API_KEY == 'REPLACE_ME': - self.response.write(REGISTRATION_INSTRUCTIONS) - else: - # Present a list of Freebase topic IDs for the query term - self.list_topics(QUERY_TERM) - - def list_topics(self, QUERY_TERM): - # Retrieve a list of Freebase topics associated with the query term - freebase_params = dict(query=QUERY_TERM, key=API_KEY) - freebase_url = FREEBASE_SEARCH_URL % urllib.urlencode(freebase_params) - freebase_response = json.loads(urllib.urlopen(freebase_url).read()) - - if len(freebase_response["result"]) == 0: - exit("No matching terms were found in Freebase.") - - # Create a page that shows a select box listing the topics. - # When the user selects a topic and submits the form, the - # 'post' method below will handle the form submission and - # retrieve videos for the selected topic. - select_topic_page = (''' - <html> - <body> - <p>The following topics were found:</p> - <form method="post"> - <select name="topic"> - ''') - for result in freebase_response["result"]: - select_topic_page += ('<option value="' + result["mid"] + '">' + - result.get("name", "Unknown") + '</option>') - - select_topic_page += ''' - </select> - <p><input type="submit" /></p> - </form> - </body> - </html> - ''' - - # Display the HTML page listing the topic choices. - self.response.out.write(select_topic_page) - - def post(self): - topic_id = self.request.get('topic') - - # Service for calling the YouTube API - youtube = build(YOUTUBE_API_SERVICE_NAME, - YOUTUBE_API_VERSION, - developerKey=API_KEY) - - # Execute the search request using default query term and retrieved topic. - search_response = youtube.search().list( - part = 'id,snippet', - type = 'video', - topicId = topic_id - ).execute() - - videos = [] - - for search_result in search_response.get("items", []): - videos.append(search_result) - - template_values = { - 'videos': videos - } - - self.response.headers['Content-type'] = 'text/html' - template = JINJA_ENVIRONMENT.get_template('index.html') - self.response.write(template.render(template_values)) - -app = webapp2.WSGIApplication([ - ('/.*', MainHandler), -], debug=True) diff --git a/ruby/README.md b/ruby/README.md new file mode 100644 index 00000000..8774d3a7 --- /dev/null +++ b/ruby/README.md @@ -0,0 +1,47 @@ +## Samples in this directory: + +### [Authorize a request](/ruby/oauth/oauth_util.rb) + +Description: The following code sample performs OAuth 2.0 authorization by checking for the presence of a local +file that contains authorization credentials. If the file is not present, the script opens a browser and waits +for a response, then saves the returned credentials locally. + +### [Add a channel subscription](/ruby/add_subscription.rb) + +Method: youtube.subscriptions.insert<br> +Description: This sample calls the API's <code>subscriptions.insert</code> method to add a subscription +to a specified channel. + +### [Post a channel bulletin](/ruby/channel_bulletin.rb) + +Method: youtube.activities.insert<br> +Description: This sample calls the API's <code>activities.insert</code> method to post a bulletin to the channel +associated with the request. + +### [Retrieve my uploads](/ruby/my_uploads.rb) + +Method: youtube.playlistItems.list<br> +Description: This sample calls the API's <code>playlistItems.list</code> method to retrieve a list of videos uploaded +to the channel associated with the request. The code also calls the <code>channels.list</code> method with the +<code>mine</code> parameter set to <code>true</code> to retrieve the playlist ID that identifies the channel's +uploaded videos. + +### [Search by keyword](/ruby/search.rb) + +Method: youtube.search.list<br> +Description: This sample calls the API's <code>search.list</code> method to retrieve search results +associated with a particular keyword. + +### [Upload a video](/ruby/upload_video.rb) + +Method: youtube.videos.insert<br> +Description: This sample calls the API's <code>videos.insert</code> method to upload a video to the channel +associated with the request. + +### [Retrieve top 10 videos by viewcount](/ruby/yt_analytics_report.rb) + +Method: youtubeAnalytics.reports.query<br> +Description: This sample calls the API's <code>reports.query</code> method to retrieve YouTube Analytics data. +By default, the report retrieves the top 10 videos based on viewcounts, and it returns several metrics for those +videos, sorting the results in reverse order by viewcount. By setting command line parameters, you can use the same +code to retrieve other reports as well. diff --git a/ruby/quickstart.rb b/ruby/quickstart.rb new file mode 100644 index 00000000..bd73fc22 --- /dev/null +++ b/ruby/quickstart.rb @@ -0,0 +1,64 @@ +# Sample Ruby code for user authorization + +require 'rubygems' +gem 'google-api-client', '>0.7' +require 'google/apis' +require 'google/apis/youtube_v3' +require 'googleauth' +require 'googleauth/stores/file_token_store' + +require 'fileutils' +require 'json' + +# REPLACE WITH VALID REDIRECT_URI FOR YOUR CLIENT +REDIRECT_URI = 'http://localhost' +APPLICATION_NAME = 'YouTube Data API Ruby Tests' + +# REPLACE WITH NAME/LOCATION OF YOUR client_secrets.json FILE +CLIENT_SECRETS_PATH = 'client_secret.json' + +# REPLACE FINAL ARGUMENT WITH FILE WHERE CREDENTIALS WILL BE STORED +CREDENTIALS_PATH = File.join(Dir.home, '.credentials', + "youtube-quickstart-ruby-credentials.yaml") + +# SCOPE FOR WHICH THIS SCRIPT REQUESTS AUTHORIZATION +SCOPE = Google::Apis::YoutubeV3::AUTH_YOUTUBE_READONLY + +def authorize + FileUtils.mkdir_p(File.dirname(CREDENTIALS_PATH)) + + client_id = Google::Auth::ClientId.from_file(CLIENT_SECRETS_PATH) + token_store = Google::Auth::Stores::FileTokenStore.new(file: CREDENTIALS_PATH) + authorizer = Google::Auth::UserAuthorizer.new( + client_id, SCOPE, token_store) + user_id = 'default' + credentials = authorizer.get_credentials(user_id) + if credentials.nil? + url = authorizer.get_authorization_url(base_url: REDIRECT_URI) + puts "Open the following URL in the browser and enter the " + + "resulting code after authorization" + puts url + code = gets + credentials = authorizer.get_and_store_credentials_from_code( + user_id: user_id, code: code, base_url: REDIRECT_URI) + end + credentials +end + +# Initialize the API +service = Google::Apis::YoutubeV3::YouTubeService.new +service.client_options.application_name = APPLICATION_NAME +service.authorization = authorize + +# Sample ruby code for channels.list + +def channels_list_by_username(service, part, **params) + response = service.list_channels(part, params).to_json + item = JSON.parse(response).fetch("items")[0] + + puts ("This channel's ID is #{item.fetch("id")}. " + + "Its title is '#{item.fetch("snippet").fetch("title")}', and it has " + + "#{item.fetch("statistics").fetch("viewCount")} views.") +end + +channels_list_by_username(service, 'snippet,contentDetails,statistics', for_username: 'GoogleDevelopers')