How upload a video to Youtube Using Google Apps Script? - google-apps-script

I want to use Google Apps Script to upload a video YouTube using the YouTube Data API v3. This is my code:
function YouTubeAPI()
{
var url = {URL VIDEO};
var file = UrlFetchApp.fetch(url).getBlob();
Logger.log(file.getName());
var snippet = {
"snippet": {
"title": "Summer vacation in California",
"description": "Had a great time surfing in Santa Cruz",
"tags": ["surfing", "Santa Cruz"],
"categoryId": "22"},"status": {"privacyStatus": "private"}};
YouTube.Videos.insert(snippet, 'snippet,status', file)
}
The response is "Unauthorized", I check the Google Console is enable, and in the Script also enable the Youtube Data API.

If you have an MP4 file in your Google Drive that is less than 50MB, then you can get the file from your Google Drive and upload it to YouTube using the YouTube Data API. I created a new Google Cloud Platform (GCP) project, and enabled the YouTube Data API and the Google Drive API, and associated the new GCP project with my Apps Script project. Also, I manually added the needed scopes to the appsscript.json file.
{
"timeZone": "Your time zone will be here",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "YouTube",
"version": "v3",
"serviceId": "youtube"
}
]
},
"oauthScopes": ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/script.external_request","https://www.googleapis.com/auth/youtube"],
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
The code:
function uploadToYouTube(mp4_fileId) {
var blob,mp4_fileId,part,requestResource,response;
var options = {},snippet = {};
/*
You will need to create a GCP standard project and associate it with this Apps Script project-
In the new code editor click the settings cog wheel and scroll down to:
Google Cloud Platform (GCP) Project -
You may get an error:
In order to change your project, you will need to configure the OAuth consent screen. Configure your OAuth Consent details.
And if you do not have a Google Workspace account then you wont be able to set up the GCP project as "INTERNAL"
You will need to enable the Google Drive API and the YouTube API in the associated GCP project -
*/
/*
This code needs the file ID of the MP4 file in your Google Drive -
To get the file ID of an MP4 video file in Google Drive, right click the MP4 in your Google Drive
and choose, "Get link"
The link will look like this:
https://drive.google.com/file/d/FILE_ID_IS_HERE/view?usp=sharing
In the URL is the file ID
*/
options = {
"method" : "get",
"headers" : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
"muteHttpExceptions":true
}
mp4_fileId = mp4_fileId ? mp4_fileId : 'PUT_YOUR_MP4_FILE_ID_HERE';
const url = `https://www.googleapis.com/drive/v3/files/` + mp4_fileId + `?alt=media`;
response = UrlFetchApp.fetch(url, options);
//Logger.log('response.getResponseCode(): ' + response.getResponseCode())
if (response.getResponseCode() !== 200) {
return;
}
blob = response.getBlob();
//Logger.log('blob.getName(): ' + blob.getName())
/*
{"snippet":{
"playlistId":"YOUR_PLAYLIST_ID",
"position":0,
"resourceId":{
"kind":"youtube#video",
"videoId":"abcdefg"
}
}
}
*/
/*
{
"snippet": {
"title": "Summer vacation in California",
"description": "Had fun surfing in Santa Cruz",
"tags": ["surfing", "Santa Cruz"],
"categoryId": "22"
},
"status": {
"privacyStatus": "private"
}
}
*/
requestResource = {};
snippet.title = "AAA_Put_Title_Here";
snippet.description = "Description of video goes here";
snippet.categoryId = "22";
options.snippet = snippet;
options.status = {
"privacyStatus": "private"
}
part = "snippet,status";//This correlates to the options
//YouTube.Videos.insert(resource: Youtube_v3.Youtube.V3.Schema.Video, part: string[], mediaData: Blob, optionalArgs: Object)
var response = YouTube.Videos.insert(requestResource, part, blob, options);
if (!response || !response.kind) {//There was an error
console.log("Error!")
}
//Logger.log('response: ' + response);
}

Try to use code below. This sample code finds the user's uploads then updates the most recent upload's description by appending a string.
/**
* This sample finds the active user's uploads, then updates the most recent
* upload's description by appending a string.
*/
function updateVideo() {
// 1. Fetch all the channels owned by active user
var myChannels = YouTube.Channels.list('contentDetails', {mine: true});
// 2. Iterate through the channels and get the uploads playlist ID
for (var i = 0; i < myChannels.items.length; i++) {
var item = myChannels.items[i];
var uploadsPlaylistId = item.contentDetails.relatedPlaylists.uploads;
var playlistResponse = YouTube.PlaylistItems.list('snippet', {
playlistId: uploadsPlaylistId,
maxResults: 1
});
// Get the videoID of the first video in the list
var video = playlistResponse.items[0];
var originalDescription = video.snippet.description;
var updatedDescription = originalDescription + ' Description updated via Google Apps Script';
video.snippet.description = updatedDescription;
var resource = {
snippet: {
title: video.snippet.title,
description: updatedDescription,
categoryId: '22'
},
id: video.snippet.resourceId.videoId
};
YouTube.Videos.update(resource, 'id,snippet');
}
}
For more information, download the demo app here: https://github.com/youtube/api-samples

Related

Cannot load SVF2 model in Autodesk Forge Viewer

I just tried out the SVF2 public beta but couldn't get the model to load in the Viewer. I believe the model was translated successfully since the manifest returned has:
"name": "XXXX_ARC.nwd",
"progress": "complete",
"outputType": "svf2",
"status": "success"
However, when I tried to load the model in Viewer, it would fail on this line:
theViewer.loadModel(svfURL, onItemLoadSuccess, onItemLoadFail);
The svfURL is something like this:
https://cdn.derivative.autodesk.com/modeldata/file/urn:adsk.fluent:fs.file:autodesk-360-translation-storage-prod/*MyURN*/output/otg_files/0/output/0/otg_model.json
And the errors I got from Chrome browser:
403 GET errors. Seems like I don't have privilege to access the model?
Is there some additional setting I need to do?
Additional Info:
I have setup the Viewer environment as follows:
var options = {
env: 'MD20ProdUS',
api: 'D3S',
getAccessToken: getForgeToken
};
var documentId = 'urn:' + urn;
Autodesk.Viewing.Initializer(options, function onInitialized() {
var htmlDiv = document.getElementById('forgeViewer');
var config3d = {
extensions: ['ToolbarExtension', 'HandleSelectionExtension', .....a few extensions ],
loaderExtensions: { svf: "Autodesk.MemoryLimited" }
};
theViewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv, config3d);
var startedCode = theViewer.start();
if (startedCode > 0) {
console.error('Failed to create a Viewer: WebGL not supported.');
return;
}
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
I have also tried removing the config3d when creating the viewer but it still returned the same messages. The code got into onDocumentLoadSuccess but failed at theViewer.loadModel(svfURL, onItemLoadSuccess, onItemLoadFail);, jumping into onItemLoadFail.
Because you mention mainly the viewer not loading the SVF2, I can suspect that maybe you have not specified the correct Viewer environment.
Here is some sample code, and pay attention to the options where you have to set env and API:
var viewer;
var options = {
// These are the SVF2 viewing settings during public beta
env: 'MD20ProdUS', // or MD20ProdEU (for EMEA)
api: 'D3S',
getAccessToken: getForgeToken
};
var documentId = 'urn:' + getUrlParameter('urn');
// Run this when the page is loaded
Autodesk.Viewing.Initializer(options, function onInitialized() {
// Find the element where the 3d viewer will live.
var htmlElement = document.getElementById('MyViewerDiv');
if (htmlElement) {
// Create and start the viewer in that element
viewer = new Autodesk.Viewing.GuiViewer3D(htmlElement);
viewer.start();
// Load the document into the viewer.
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
}
});
I am facing the same problem.
Although the model has been converted to SVF2 format, my cloud credits are being used up .
An excerpt from the manifest:
"name": "7085-33cc-9464.rvt",
"progress": "complete",
"outputType": "svf2",
"status": "success"
No matter with which settings, only the SVF format is loaded in the viewer.
I don't get an error message from the viewer, everything works as before, except that SVF is still loaded and not SVF2.
Viewer init options:
const viewerEnv = await this.initialize({
//env: dbModel.env,
env: "MD20ProdEU",
api: "D3S",
//accessToken: "",
});
Not sure if this has been solved on a separate thread, but the issue was probably that acmSessionId was not set in the options for loadModel() - see https://forge.autodesk.com/blog/403-error-when-trying-view-svf2
function onDocumentLoadSuccess(doc) {
let items = doc.getRoot().search({
'type': 'geometry',
'role': '3d'
}, true)
let url = doc.getViewablePath(items[0])
viewer.loadModel(url, { acmSessionId: doc.getAcmSessionId(url) })
}
The best thing is to just use loadDocumentNode() instead of loadModel()

My GSM Gmail addon does not show the card

I have built a GSM add on and published it for my domain. I built the code, on Google Apps Script and set it up in Google API Console. I installed it for my domain, but it does not show the card in Gmail It should show in the sidebar, and in the compose window. It works fine when I install the head version from within google apps script, but then I publish it to GSM for users in my organization it doesn't work. The purpose of the addon is to collect information from fields in a card and use that in an email template. I think it might be something to do with OAuth scopes but I am not sure. This is my very first GSM project and I don't know what OAuth scopes I should declare in my code and in the Google API console.
Here is my code, I have 2 files, appscript.json, and code.gs.
code.gs:
function onGmailCompose(e) {
console.log(e);
var header = CardService.newCardHeader()
.setTitle('Use Template')
.setSubtitle('Use the template for sending an email after a review has been published.');
// Create text input for entering the cat's message.
var input2 = CardService.newTextInput()
.setFieldName('FName')
.setTitle('First Name')
.setHint('What is the readers first name?');
var input3 = CardService.newTextInput()
.setFieldName('BookTitle')
.setTitle('Reviewed Book Title')
.setHint('What is the title of the book reviewed?');
var input4 = CardService.newTextInput()
.setFieldName('BookAuthor')
.setTitle('Reviewed Book Author')
.setHint('Who is the author of the book reviewed?');
// Create a button that inserts the cat image when pressed.
var action = CardService.newAction()
.setFunctionName('useTemplate');
var button = CardService.newTextButton()
.setText('Use Template')
.setOnClickAction(action)
.setTextButtonStyle(CardService.TextButtonStyle.FILLED);
var buttonSet = CardService.newButtonSet()
.addButton(button);
// Assemble the widgets and return the card.
var section = CardService.newCardSection()
.addWidget(input2)
.addWidget(input3)
.addWidget(input4)
.addWidget(buttonSet);
var card = CardService.newCardBuilder()
.setHeader(header)
.addSection(section);
return card.build();
}
function useTemplate(e) {
console.log(e);
var FName = e.formInput.FName;
var Title = e.formInput.BookTitle;
var Author = e.formInput.BookAuthor;
var now = new Date();
var htmlIntro = '<p>Hello, ';
var html2 = ' Thank you for writing a book review at Good Book Reviews on ';
var html3 = ' by ';
var html4 = '. You Review has been published to our site. Any personal information you included was NOT published, including first name, last name, age, and email address. Only info you wrote about the book was published. You can see it right here! If you need anything else, feel free to contact us at support#goodbookreviews.page or reply to this email to contact us. <br> Happy Reading,<br> The Book Review Team</p>';
var message = htmlIntro + FName + html2 + Title + html3 + Author + html4;
var response = CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftBodyAction(CardService.newUpdateDraftBodyAction()
.addUpdateContent(message, CardService.ContentType.MUTABLE_HTML)
.setUpdateType(CardService.UpdateDraftBodyType.IN_PLACE_INSERT))
.build();
return response;
}
function onGmailMessage(e) {
console.log(e);
var header = CardService.newCardHeader()
.setTitle('Unavailable')
.setSubtitle('Open the compose window to use template');
var card = CardService.newCardBuilder()
.setHeader(header);
return card.build();
}
appscript.json:
{
"timeZone": "America/Chicago",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/gmail.compose"],
"runtimeVersion": "V8",
"addOns": {
"common": {
"name": "Review Published Email Template",
"logoUrl": "https://goodbookreviews.page/Logo.png",
"useLocaleFromApp": true,
"universalActions": [{
"label": "Book Review ",
"openLink": "https://www.goodbookreviews.page"
}]
},
"gmail": {
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "onGmailMessage"
}],
"composeTrigger": {
"selectActions": [{
"text": "Use Template",
"runFunction": "onGmailCompose"
}],
"draftAccess": "NONE"
}
}
}
}
The scopes that I specify in the OAuth consent screen page are here:
email
profile
openid
https://www.googleapis.com/auth/gmail.compose
The Email, profile, and openid are added by default and they are mandatory
Here are the scopes that I specify in the configuration page of the GSM SDK.
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/gmail.compose
You are very close to making this add-on works. You only need to add some scopes to your manifest file. Your final manifest should look similar to this one:
{
"timeZone":"America/Chicago",
"dependencies":{
},
"exceptionLogging":"STACKDRIVER",
"oauthScopes":[
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.addons.current.action.compose",
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/script.locale"
],
"runtimeVersion":"V8",
"addOns":{
"common":{
"name":"Review Published Email Template",
"logoUrl":"https://goodbookreviews.page/Logo.png",
"useLocaleFromApp":true,
"universalActions":[
{
"label":"Book Review ",
"openLink":"https://www.goodbookreviews.page"
}
]
},
"gmail":{
"contextualTriggers":[
{
"unconditional":{
},
"onTriggerFunction":"onGmailMessage"
}
],
"composeTrigger":{
"selectActions":[
{
"text":"Use Template",
"runFunction":"onGmailCompose"
}
],
"draftAccess":"NONE"
}
}
}
}
The rest of your code is correct. I deployed your add-on with this manifest, and it worked. Don't hesitate to ask any additional doubt if you need further clarification.

Upload file to Google Team Drive from php or html form

I am considering using either php or html form with an upload button to upload a file to a Google Team Drive.
I am wondering if anyone has had success using the Google Picker API? If so, would you kindly share your solution.
This is the info I'm referencing on Google API
Thank you for your help!
After hours of trial and error, I got it working.
My code is posted below.
Remember to set your URLs in OAuth settings
These are mine using MAMP:
http://localhost:8888
http://localhost:8888/printing-forms/custom-stationery
TEAM DRIVE ID is found in Team Drive URL
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google File Upload for Custom Stationary Orders</title>
<script type="text/javascript">
// The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key.
var developerKey = '[your API key]';
// The Client ID obtained from the Google API Console. Replace with your own Client ID.
var clientId = "[your Client ID]"
// Replace with your own project number from console.developers.google.com.
// See "Project number" under "IAM & Admin" > "Settings"
var appId = "[your project number]";
// Scope to use to access user's Drive items.
var scope = ['https://www.googleapis.com/auth/drive.file'];
var pickerApiLoaded = false;
var oauthToken;
// Use the Google API Loader script to load the google.picker script.
function loadPicker() {
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for searching images.
function createPicker() {
if (pickerApiLoaded && oauthToken) {
// var view = new google.picker.View(google.picker.ViewId.DOCS);
// view.setMimeTypes("pdf");
var picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.SUPPORT_TEAM_DRIVES)
// Hide list of files on the Drive
.enableFeature(google.picker.Feature.MINE_ONLY)
// Hide the Upload option for Personal Drive
// .enableFeature (google.picker.Feature.NAV_HIDDEN)
.addView(new google.picker.DocsUploadView()
// Custom Stationary Folder
.setParent('[Team Drive ID from URL]'))
.addView(new google.picker.DocsView(google.picker.ViewId.DOCS)
.setEnableTeamDrives(true)
.setSelectFolderEnabled(false)
// Upload files to Custom Stationary Folder
.setParent('[Team Drive ID from URL]'))
// Select more than one file
// .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId(appId)
.setOAuthToken(oauthToken)
// Displays Personal Drive folder
// .addView(view)
.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].name;
// Show the ID of the Google Drive folder
document.getElementById('result').innerHTML = fileId + ' has been successfully uploaded';
// location.reload();
// alert('The user selected: ' + fileId);
}
}
</script>
</head>
<body>
<div id="result"></div>
<div id="continue">Back to the Order Form</div>
<!-- The Google API Loader script. -->
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=loadPicker"></script>
</body>
</html>

Public Google Script doesn't run from a chrome extension

I have a Google script which modifies the content of a google spreadsheet (it's not the final script of course), but I have a problem to run this simple script from a google chrome extension.
Here is the script attached to my spreadsheet :
function insertData(parameters) {
var spreadsheet = SpreadsheetApp.openByUrl(THE_URL_OF_THE_SPREADSHEET)
spreadsheet.getRange('A5').activate();
spreadsheet.getCurrentCell().setValue(parameters.data1);
spreadsheet.getRange('B5').activate();
spreadsheet.getCurrentCell().setValue(parameters.data2);
}
I deployed this script both as a web app (execute as me + access to everyone, even anonymous) and as an executable API (access to anyone).
Then I tried this JS script to run my google script, from a google chrome extension, using this code that I got from an google chrome extension example:
sendDataToExecutionAPICallback: function() {
post({ 'url': 'https://script.googleapis.com/v1/scripts/' + SCRIPT_ID + ':run',
'callback': obj.executionAPIResponse,
'token': 'MY_GENERATED_TOKEN'
'request': {
'function': 'insertData',
'parameters': {
'data1': 'ok1 from script',
'data2': 'ok2 from script'
},
'devMode': true
}
});
},
executionAPIResponse: function(response){
var obj = this;
var info;
if (response.response.result.status == 'ok'){
info = 'Data has been entered';
} else {
info = 'Error...';
}
obj.displayMessage(info);
}
And I have this post function :
function post(options) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// JSON response assumed. Other APIs may have different responses.
options.callback(JSON.parse(xhr.responseText));
} else if(xhr.readyState === 4 && xhr.status !== 200) {
console.log('post', xhr.readyState, xhr.status, xhr.responseText);
}
};
xhr.open('POST', options.url, true);
// Set standard Google APIs authentication header.
xhr.setRequestHeader('Authorization', 'Bearer ' + options.token);
xhr.send(JSON.stringify(options.request));
}
And when I call the sendDataToExecutionAPICallback function, I got this auth error:
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
EDIT1:
After generating a token, and added it to my code, I have this error:
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
I would like to propose two methods. Please choose one of them.
Method 1: Execution API (Method: scripts.run of Apps Script API)
In order to run the functions using Execution API, please carry out the following flow.
Create a project (standalone or bound script).
Copy and paste your GAS script to the project. In this case, your GAS script was not modified.
Deploy API executable. As a sample, choose "Only myself" as "Who has access to the script"
Enable Apps Script API at API console.
Using the save button, save the project on the script editor. This is an important point. By this, the scripts are reflected to the deployed execution API.
Retrieve access token from client ID and client secret of this project.
Please include https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/drive.scripts, https://www.googleapis.com/auth/spreadsheets in the scopes. https://www.googleapis.com/auth/drive might not be used for this situation.
Please use the retrieved access token to the following modified script.
Javascript :
sendDataToExecutionAPICallback: function() {
post({
'url': 'https://script.googleapis.com/v1/scripts/' + SCRIPT_ID + ':run',
'callback': obj.executionAPIResponse,
'token': 'MY_GENERATED_TOKEN',
'request': {
'function': 'insertData',
'parameters': [{
'data1': 'ok1 from script',
'data2': 'ok2 from script',
}],
'devMode': true,
}
});
},
executionAPIResponse: function(response) {
var info;
if (!response.error) {
info = 'Data has been entered';
} else {
info = 'Error...';
}
obj.displayMessage(info);
},
function post(options) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// JSON response assumed. Other APIs may have different responses.
options.callback(JSON.parse(xhr.responseText));
} else if(xhr.readyState === 4 && xhr.status !== 200) {
console.log('post', xhr.readyState, xhr.status, xhr.responseText);
}
};
xhr.open('POST', options.url, true);
// Set standard Google APIs authentication header.
xhr.setRequestHeader('Authorization', 'Bearer ' + options.token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(options.request));
}
Note :
In my environment, this modified script works. But if in your environment, this didn't work, could you please try the method 2?
Method 2: Web Apps
From your question and comments, I'm worried that the access token might not be able to be used. So I would like to also propose the method using Web Apps. In this case, you can run the function without the access token. But the password key to run is used at the payload.
When you use this, please deploy Web Apps as "Execute the app as:" : Me and "Who has access to the app:": Anyone, even anonymous. If you want to know the detail information of Web Apps, please check here.
In this sample script, it doesn't use xhr.setRequestHeader('Authorization', 'Bearer ' + options.token); of post().
The modified script is as follows. Please put GAS to the script editor which deploys Web Apps.
GAS :
function doPost(e) {
var p = JSON.parse(e.postData.contents);
if (p.password == 'samplePassword') {
insertData(p.parameters);
return ContentService.createTextOutput("ok");
} else {
return ContentService.createTextOutput("error");
}
}
function insertData(parameters) {
var spreadsheet = SpreadsheetApp.openByUrl(THE_URL_OF_THE_SPREADSHEET);
spreadsheet.getRange('A5').activate();
spreadsheet.getCurrentCell().setValue(parameters.data1);
spreadsheet.getRange('B5').activate();
spreadsheet.getCurrentCell().setValue(parameters.data2);
}
Javascript :
sendDataToExecutionAPICallback: function() {
post({
'url': 'https://script.google.com/macros/s/#####/exec', // URL of Web Apps
'callback': obj.executionAPIResponse,
'request': {
'parameters': {
'data1': 'ok1 from script',
'data2': 'ok2 from script'
},
'password': 'samplePassword',
}
});
},
executionAPIResponse: function(response) {
var info;
if (response == 'ok') {
info = 'Data has been entered';
} else {
info = 'Error...';
}
obj.displayMessage(info);
},
function post(options) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// JSON response assumed. Other APIs may have different responses.
options.callback(xhr.responseText);
} else if(xhr.readyState === 4 && xhr.status !== 200) {
console.log('post', xhr.readyState, xhr.status, xhr.responseText);
}
};
xhr.open('POST', options.url, true);
// Set standard Google APIs authentication header.
xhr.send(JSON.stringify(options.request));
}
Note :
If you use Web Apps, after copied and pasted the GAS script to the script editor, please redeploy Web Apps as a new version. By this, the latest script is reflected.
In my environment, I could confirm that both methods worked.

OAuth2 with Google Cloud Print

I had written a Google Apps Script that connected to Google Cloud Print to automate some printing. The script would auto-run on a time interval, search for relevant files, and if found it would sent them to my printer. My code used OAuthConfig and was working fine, but now that class has been deprecated and after a weekend of trial & error and scouring the interwebs I can't get it to work with OAuth2.
Here's the OAuthConfig code that was working fine:
function printDoc(docId, docTitle, myPrinterId) {
var scope = 'https://www.googleapis.com/auth/cloudprint';
var url = 'https://www.google.com/cloudprint/submit';
var payloadOfSubmit = {
"printerid" : myPrinterId,
"title" : docTitle,
"content" : docId,
"contentType" : "google.kix"
};
var fetchArgs = googleOAuth_('google', scope, payloadOfSubmit);
fetchArgs.method = 'POST';
var responseOfSubmit = UrlFetchApp.fetch(url, fetchArgs);
var jsonOfSubmit = JSON.parse(responseOfSubmit.getContentText());
return jsonOfSubmit;
}
function googleOAuth_(name, scope, payloadData) {
var oAuthConfig = UrlFetchApp.addOAuthService(name);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey("anonymous");
oAuthConfig.setConsumerSecret("anonymous");
return {
oAuthServiceName:name,
oAuthUseToken:"always",
muteHttpExceptions:true,
payload:payloadData
};
}
I've successfully connected the github library for OAuth2. However, what's different about the instructions provided there, and on many other sites, is that they assume that the code will be deployed as a web service where a user is prompted to manually click to authorize the request. In my case the code will be saved on a Google Apps Script file, and the Cloud Printer is on the same Google account, so I never needed this manual intervention or back & forth with my original OAuthconfig.
My first attempt by adapting the instructions was:
function printDoc2(docId, docTitle, myPrinterId) {
var url = 'https://www.google.com/cloudprint/submit';
var scope = 'https://www.googleapis.com/auth/cloudprint';
var payloadOfSubmit = {
"printerid" : myPrinterId,
"title" : docTitle,
"content" : docId,
"contentType" : "google.kix",
};
var accessToken = googleOAuth_('google', scope).getAccessToken();
var params = {
method:"POST",
headers: {"Authorization": "Bearer " + accessToken},
muteHttpExceptions:true,
payload:payloadOfSubmit
};
var responseOfSubmit = UrlFetchApp.fetch(url, params);
//Logger.log(responseOfSubmit);
var jsonOfSubmit = JSON.parse(responseOfSubmit.getContentText());
return jsonOfSubmit;
}
function googleOAuth2_(name, scope) {
return OAuth2.createService(name)
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId("anonymous")
.setClientSecret("anonymous")
.setProjectKey(ScriptApp.getProjectKey())
.setPropertyStore(PropertiesService.getUserProperties())
.setScope(scope)
.setCallbackFunction('authCallback');
}
function authCallback(request) {
var driveService = getDriveService();
var isAuthorized = driveService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
But this gives me an error "Access not granted or expired" when it tries to run the line:
var accessToken = googleOAuth_('google', scope).getAccessToken();
So I found a apps ScriptApp Method getOAuthToken which seemed like it might give me the token I need. I replaced the above line with:
var accessToken = ScriptApp.getOAuthToken();
And the code executes but my response from the server is "Error 403 User credentials required".
Here's my third attempt based on #Mogsdad's suggestion:
function sendPrintJob(docId,myPrinterId,docTitle) {
var payloadOfSubmit = {
"printerid" : myPrinterId,
"title" : docTitle,
"content" : docId,
"contentType" : "google.kix" ,
};
var request = {
"method": "POST",
"headers":{"Authorization": "Bearer "+ScriptApp.getOAuthToken()},
"muteHttpExceptions": true
};
var responseOfSubmit = UrlFetchApp.fetch("https://www.google.com/cloudprint/submit", request);
Logger.log(responseOfSubmit);
}
I've tried a number of variations, including creating a Developer Console Project and using the Client ID provided there, but I keep getting stuck at these two issues (access not granted, or credentials required). If anyone can provide any help I'd really appreciate it.
Here are the steps that allowed me to connect Google Apps Script to Google Cloud Print, so I could then submit GCP jobs (these steps are all started from within Google Apps Script):
Add the OAuth2 library
(https://github.com/googlesamples/apps-script-oauth2) to your Google
Apps Script by going to: Resources > Libraries > Find Library
MswhXl8fVhTFUH_Q3UOJbXvxhMjh3Sh48 > Select
Create new web application in Developer Console Resources > Developer Console Project > Click the project link > APIs & Auth >
Credentials > Add Credentials > OAuth2.0 Client ID > Web
Application > Set Authorized redirect URIs to the format
https://script.google.com/macros/d/{PROJECT KEY}/usercallback
where project key is under File > Project Properties and copy
your client ID and client secret
Add the ID and Secret to "getCloudPrintService()" code below (replace client_id and client_secret)
Go to Run > ShowURL and authorize the script.
Open the Logger (Cmd + Enter), copy the URL and paste it in a new browser tab to complete the authorization.
Go to https://www.google.com/cloudprint/#printers , select your printer, click details, expand advanced details, and copy your Printer ID (it will be of the format 555aa555-5a55-5555-5555-55555a55a555)
Add the printer id to "printGoogleDocument()" code below (replace myPrinterId)
This resource was helpful in figuring the steps out: http://ctrlq.org/code/20061-google-cloud-print-with-apps-script, and you may also find these links helpful:
https://developers.google.com/cloud-print/docs/appInterfaces
https://mashe.hawksey.info/2015/10/setting-up-oauth2-access-with-google-apps-script-blogger-api-example/
function showURL() {
var cpService = getCloudPrintService();
if (!cpService.hasAccess()) {
Logger.log(cpService.getAuthorizationUrl());
} else {
Logger.log("You already have access to this service.");
}
}
function getCloudPrintService() {
return OAuth2.createService('print')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(client_id)
.setClientSecret(client_secret)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('https://www.googleapis.com/auth/cloudprint')
.setParam('login_hint', Session.getActiveUser().getEmail())
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
var isAuthorized = getCloudPrintService().handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('You can now use Google Cloud Print from Apps Script.');
} else {
return HtmlService.createHtmlOutput('Cloud Print Error: Access Denied');
}
}
function getPrinterList() {
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/search', {
headers: {
Authorization: 'Bearer ' + getCloudPrintService().getAccessToken()
},
muteHttpExceptions: true
}).getContentText();
var printers = JSON.parse(response).printers;
for (var p in printers) {
Logger.log("%s %s %s", printers[p].id, printers[p].name, printers[p].description);
}
}
function printGoogleDocument(docId, docTitle) {
// For notes on ticket options see https://developers.google.com/cloud-print/docs/cdd?hl=en
var ticket = {
version: "1.0",
print: {
color: {
type: "STANDARD_COLOR"
},
duplex: {
type: "NO_DUPLEX"
},
}
};
var payload = {
"printerid" : myPrinterId,
"content" : docId,
"title" : docTitle,
"contentType" : "google.kix", // allows you to print google docs
"ticket" : JSON.stringify(ticket),
};
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/submit', {
method: "POST",
payload: payload,
headers: {
Authorization: 'Bearer ' + getCloudPrintService().getAccessToken()
},
"muteHttpExceptions": true
});
// If successful, should show a job here: https://www.google.com/cloudprint/#jobs
response = JSON.parse(response);
if (response.success) {
Logger.log("%s", response.message);
} else {
Logger.log("Error Code: %s %s", response.errorCode, response.message);
}
return response;
}
The scope "https://www.googleapis.com/auth/cloudprint" has to be included explicitly in the
manifest file
appscript.json (View > Show manifest file)
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"oauthScopes": [
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/cloudprint"
],
"exceptionLogging": "STACKDRIVER"
}
Code.gs
function listPrinters() {
var options = {
headers: {
authorization: 'OAuth ' + ScriptApp.getOAuthToken()
}
}
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/search', options);
Logger.log(response);
}