Get URL parameter passed to google form via apps script - google-apps-script

I`m trying to get URL parameter passed to google form via apps script, but don't know exactly how does it does. Case:
https://docs.google.com/forms/d/e/XXXX/viewform?a=12
(Passed XXXX, cause this form is for clients use)
and I need to get answers and this parameter after submit.
function onSubmit(e) {
var POST_UR​L = "www.myserver.com?id=a";//this parametr
var form = FormApp.getActiveForm();
var allResponses = form.getResponses();
var latestResponse = allResponses[allResponses.length - 1];
var response = latestResponse.getItemResponses();
var payload = {};
for (var i = 0; i < response.length; i++) {
var question = response[i].getItem().getTitle();
var answer = response[i].getResponse();
payload[question] = answer;
}
var options = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify(payload)
};
UrlFetchApp.fetch(POST_URL, options);
};
​​
This code made for sending POST request with answers of form to my server and how can I pass the ​a=12 or only 12 to this request?

You need to append the values to POST_URL instead of passing via payload. There's a nice bit of code provided by Google in their OAuth2 library that will do this for you:
/**
* Builds a complete URL from a base URL and a map of URL parameters.
* #param {string} url The base URL.
* #param {Object.<string, string>} params The URL parameters and values.
* #return {string} The complete URL.
* #private
*/
function buildUrl_(url, params) {
var paramString = Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
return url + (url.indexOf('?') >= 0 ? '&' : '?') + paramString;
}
Which means you can do something like:
var POST_URL = "www.myserver.com?id=a";
var payload = {a: 12};
console.log(buildUrl_(POST_URL, payload)); // www.myserver.com?id=a&a=12
function buildUrl_(url, params) {
var paramString = Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
return url + (url.indexOf('?') >= 0 ? '&' : '?') + paramString;
}
Ultimately, the final line in your code would be:
UrlFetchApp.fetch(buildUrl_(POST_URL, payload), options);

Related

How to convert Google drive image into correct format to post it on Instagram using Google Apps Script?

I want to post images on Instagram, and for that, I followed a well-detailed StackOverflow answer. The images which I am trying to post on Instagram are coming from my Google drive folder (publicly shared). The code looks like this:
function instapost() {
const access_token = '########....######';
const instagram_business_account = '########';
const image = 'https://drive.google.com/uc?export=view&id=1SNy876_kwrFBUCZdGfPLaKx6ZdKtYwn0';
const text = 'subtitle';
var formData = {
'image_url': image,
'caption': text,
'access_token': access_token
};
var options = {
'method' : 'post',
'payload' : formData
};
const container = 'https://graph.facebook.com/v14.0/' + instagram_business_account + '/media';
// return;
const response = UrlFetchApp.fetch(container, options);
const creation = response.getContentText();
var data = JSON.parse(creation);
var creationId = data.id
var formDataPublish = {
'creation_id': creationId,
'access_token': access_token
};
var optionsPublish = {
'method' : 'post',
'payload' : formDataPublish
};
const sendinstagram = 'https://graph.facebook.com/v14.0/' + instagram_business_account + '/media_publish';
UrlFetchApp.fetch(sendinstagram, optionsPublish);
}
When I run the script, I receive the following error:
Exception: Request failed for https://graph.facebook.com returned code
400. Truncated server response: {"error":{"message":"Only photo or video can be accepted as media
type.","type":"OAuthException","code":9004,"error_subcode":2207052,"is_transient"...
(use muteHttpExceptions option to examine full response)
I followed different sources (s1 - s2) to publicly access the G-Drive image but it is getting the same error every time, kindly can you guide me on how to convert this image so that it can be posted from Google Drive folder directly.
When I saw your provided official document, the values are required to be the query parameter. Ref But, in your script, the values are sent as form instead of the query parameter. I thought that this might be the reason for your current issue.
When this is reflected in your script, how about the following modification?
Modified script:
function instapost() {
// Ref: https://gist.github.com/tanaikech/70503e0ea6998083fcb05c6d2a857107
String.prototype.addQuery = function (obj) {
return this + Object.keys(obj).reduce(function (p, e, i) {
return p + (i == 0 ? "?" : "&") +
(Array.isArray(obj[e]) ? obj[e].reduce(function (str, f, j) {
return str + e + "=" + encodeURIComponent(f) + (j != obj[e].length - 1 ? "&" : "")
}, "") : e + "=" + encodeURIComponent(obj[e]));
}, "");
}
const access_token = '########....######';
const instagram_business_account = '########';
const image = 'https://drive.google.com/uc?export=view&id=1SNy876_kwrFBUCZdGfPLaKx6ZdKtYwn0'; // or "https://drive.google.com/uc?id=1SNy876_kwrFBUCZdGfPLaKx6ZdKtYwn0&export=download"
const text = 'subtitle';
var query1 = {
'image_url': image,
'caption': text,
'access_token': access_token
};
const container = 'https://graph.facebook.com/v14.0/' + instagram_business_account + '/media';
const endpoint1 = container.addQuery(query1);
const response = UrlFetchApp.fetch(endpoint1, { method: 'post' });
const creation = response.getContentText();
var data = JSON.parse(creation);
var creationId = data.id
var query2 = {
'creation_id': creationId,
'access_token': access_token
};
const sendinstagram = 'https://graph.facebook.com/v14.0/' + instagram_business_account + '/media_publish';
const endpoint2 = sendinstagram.addQuery(query2);
UrlFetchApp.fetch(endpoint2, { method: 'post' });
}
Note:
I think that the request for this modified script is the same as the sample HTTP requests of the official document you provided. But, unfortunately, I cannot test this script. So, when an error occurs, please confirm the values of query parameters and your access token again.
If your image URL cannot be used, please test the following URL. In this case, please enable Drive API at Advanced Google services.
const image = Drive.Files.get("1SNy876_kwrFBUCZdGfPLaKx6ZdKtYwn0").thumbnailLink.replace(/\=s.+/, "=s1000");
References:
fetch(url, params)
Content Publishing

How to generate redirect URI for Smartsheet to pass data to Apps Script

Screenshot of smartsheet and google setup screens
When attempting to get data out of Smartsheet, I'm encountering an error that says the redirect URI is missing or invalid when I follow the link that was logged by my apps script project.
I've generated a client ID and client secret on both google and smartsheet but I don't know what to do next.
Google Credentials:
I'm not sure what to put in the redirect Url section or the authorized Javascript origins at the link below.
https://console.developers.google.com/apis/credentials/oauthclient/########################2d.apps.googleusercontent.com?project=project-id-##############
Smartsheet Credentials:
I have activated my Smartsheet Developer profile and generated a client ID and client secret for my app that I've called 'Google Sheets'
Shown below is the code that I have right now which I found on gitHub.
var CLIENT_ID = '...'; // what do I put here?
var CLIENT_SECRET = '...'; // what do I put here?
/**
* Authorizes and makes a request to the Smartsheet API.
*/
function run()
{
var service = getService();
if (service.hasAccess())
{
var url = 'https://api.smartsheet.com/2.0/users/me';
var response = UrlFetchApp.fetch(url,
{
headers:
{
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
}
else
{
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
}
}
/**
* Reset the authorization state, so that it can be re-tested.
*/
function reset()
{
getService().reset();
}
/**
* Configures the service.
*/
function getService()
{
return OAuth2.createService('Smartsheet')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://app.smartsheet.com/b/authorize')
.setTokenUrl('https://api.smartsheet.com/2.0/token')
// Set the client ID and secret.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function that should be invoked to
// complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Scopes to request
.setScope('READ_SHEETS')
// Set the handler for adding Smartsheet's required SHA hash parameter to
// the payload:
.setTokenPayloadHandler(smartsheetTokenHandler);
}
/**
* Handles the OAuth callback.
*/
function authCallback(request)
{
var service = getService();
var authorized = service.handleCallback(request);
if (authorized)
{
return HtmlService.createHtmlOutput('Success!');
}
else
{
return HtmlService.createHtmlOutput('Denied.');
}
}
/**
* Adds the Smartsheet API's required SHA256 hash parameter to the access token
* request payload.
*/
function smartsheetTokenHandler(payload)
{
var codeOrRefreshToken = payload.code ? payload.code : payload.refresh_token;
var input = CLIENT_SECRET + '|' + codeOrRefreshToken;
var hash = Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8);
hash = hash.map(function(val)
{
// Google appears to treat these as signed bytes, but we need them
// unsigned.
if (val < 0)
{
val += 256;
}
var str = val.toString(16);
// pad to two hex digits:
if (str.length == 1)
{
str = '0' + str;
}
return str;
});
payload.hash = hash.join('');
// The Smartsheet API doesn't need the client secret sent (secret is verified
// by the hash).
if (payload.client_secret)
{
delete payload.client_secret;
}
return payload;
}
/**
* Logs the redict URI to register.
*/
function logRedirectUri()
{
Logger.log(OAuth2.getRedirectUri());
}
function dataHandler(thing)
{
thing = getData2();
var rowTemp = thing.split(','), i, j, chunk = 7, rows = [];
for (i=0,j=rowTemp.length; i<j; i+=chunk)
{
for(var k = 0; k<2; k++)
{
rowTemp[k+2] = new Date(rowTemp[k+2])
}
rows.push(rowTemp.slice(i,i+chunk));
}
Logger.log(rows);
}
var CLIENT_ID = 'SmartSheet Client ID'; // I'm not sure if this is
// supposed to come from google
// or smartsheet
var CLIENT_SECRET = 'Smartsheet Client Secret'; // Same here
/**
* Authorizes and makes a request to the Smartsheet API.
*/
function run() {
var service = getService();
if (service.hasAccess()) {
var url = 'https://api.smartsheet.com/2.0/users/me';
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s',
authorizationUrl);
}
}
/**
* Reset the authorization state, so that it can be re-tested.
*/
function reset() {
getService().reset();
}
/**
* Configures the service.
*/
function getService()
{
return OAuth2.createService('Smartsheet')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://app.smartsheet.com/b/authorize')
.setTokenUrl('https://api.smartsheet.com/2.0/token')
// Set the client ID and secret.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function that should be invoked to
// complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Scopes to request
.setScope('READ_SHEETS')
// Set the handler for adding Smartsheet's required SHA hash parameter to
// the payload:
.setTokenPayloadHandler(smartsheetTokenHandler);
}
/**
* Handles the OAuth callback.
*/
function authCallback(request)
{
var service = getService();
var authorized = service.handleCallback(request);
if (authorized)
{
return HtmlService.createHtmlOutput('Success!');
}
else
{
return HtmlService.createHtmlOutput('Denied.');
}
}
/**
* Adds the Smartsheet API's required SHA256 hash parameter to the access token
* request payload.
*/
function smartsheetTokenHandler(payload)
{
var codeOrRefreshToken = payload.code ? payload.code : payload.refresh_token;
var input = CLIENT_SECRET + '|' + codeOrRefreshToken;
var hash = Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8);
hash = hash.map(function(val)
{
// Google appears to treat these as signed bytes, but we need them
// unsigned.
if (val < 0)
{
val += 256;
}
var str = val.toString(16);
// pad to two hex digits:
if (str.length == 1)
{
str = '0' + str;
}
return str;
});
payload.hash = hash.join('');
// The Smartsheet API doesn't need the client secret sent (secret is verified
// by the hash).
if (payload.client_secret)
{
delete payload.client_secret;
}
return payload;
}
/**
* Logs the redict URI to register.
*/
function logRedirectUri()
{
Logger.log(OAuth2.getRedirectUri());
}
I don't know much about Apps Script or the library that you are using, but you need to find the actual callback URI used by Apps Script and register that as the App Redirect URL in Smartsheet. It looks like the callback should be in the form https://script.google.com/macros/d/{SCRIPT ID}/usercallback (at least according to the library docs). That should issue the redirect which will eventually call your library authCallback with the authorization code for the token.
Here's another useful document of the process (but uses Node). https://developers.smartsheet.com/blog/creating-a-smartsheet-o-auth-flow-in-node-js
This is a complicated process that I have documented here: https://smartsheet-platform.github.io/api-docs/#third-party-app-development
If you still have questions after looking at this documentation/tutorial section, please keep asking. I'm here to help.

calling Drive REST API Version 3 via URL-fetch in apps script delivers only default attributes

I am calling Drive REST API Version 3 via URL-fetch in apps script. I am calling Files list method and query for files. Querying is working, but I only get the default attributes of the files in the api's response. If I am using fields parameter to get more fields it is just ignored.
var retVal =[];
var baseUrl = "https://www.googleapis.com/drive/v3/files";
var token = ScriptApp.getOAuthToken();
var options = {
method: "GET",
headers: {"Authorization": "Bearer " + token},
}
var maxResults = 100;
var params = {
q: query,
pageSize: maxResults,
fields: 'nextPageToken,incompleteSearch,files(kind,id,name,mimeType,starred,trashed)',
};
do {
var queryString = Object.keys(params).map(function(p) {
return [encodeURIComponent(p), encodeURIComponent(params[p])].join("=");
}).join("&");
var apiUrl = baseUrl + "?" + queryString;
Logger.log(apiUrl);
var response = JSON.parse(UrlFetchApp.fetch( apiUrl,
options).getContentText());
//Logger.log(response);
response.files.forEach(function(fileObj) {
retVal.push(fileObj);
})
params['pageToken'] = response.nextPageToken;
} while (params.pageToken);
Logger.log(retVal);
return retVal
Encoded Query: https://www.googleapis.com/drive/v3/files?q=name%20contains%20%22Test%20%2F%20Blub%2033%22%20and%20not%20mimeType%20%3D%20%22application%2Fvnd.google-apps.folder%22%20and%20trashed%20%3D%20false&pageSize=100&fields=nextPageToken%2CincompleteSearch%2Cfiles(kind%2Cid%2Cname%2CmimeType%2Cstarred%2Ctrashed)&orderBy=folder%2CmodifiedTime%20desc%2Ctitle&supportsTeamDrives=false&includeTeamDriveItems=false
test results from API: [{kind=drive#file, name=Kopie von Test / Blub 33, id=1oTbd78Bn7R7Xjo6TEAAyZmE5CjwdgRMT, mimeType=application/json}, {kind=drive#file, name=Test / Blub 33, id=12IpttBvSY-Z31ueNqG_Dmb46dXH5udcl, mimeType=application/json}, {kind=drive#file, name=Test / Blub 33, id=1FqKyDFT0bpp1JuAj3WeSV6AL-b12X4vb, mimeType=application/json}]
can someone help why the api is ignoring fields parameter?
Try replacing & with & when evaluation the queryString value.
var queryString = Object.keys(params).map(function(p) {
return [encodeURIComponent(p), encodeURIComponent(params[p])].join("=");
}).join("&");

How to send a draft email using google apps script

I am working with Google apps script and would like to create a script which picks up mail from the drafts and sends them if they have label "send-tomorrow".
Finding drafts with a certain label is pretty simple:
var threads = GmailApp.search('in:draft label:send-tomorrow');
However I don't see an API to send the message!
The only option I see is to:
- open the message
- extract body/attachments/title/from/to/cc/bcc
- send a new message with the above params
- destroy the previous draft
which seems pretty annoying and I'm not sure would work well with embedded images, multiple attachments etc...
any hint?
The only option I see is to: - open the message - extract body/attachments/title/from/to/cc/bcc - send a new message with the above params - destroy the previous draft
This is the exact topic of this blog by Amit Agarawal. His script does just what you describe, but doesn't handle inline images. For those, you can adapt the code from this article.
But you're right - what's the point of even having a draft message if you can't just send the stupid thing?!
We can use the GMail API Users.drafts: send from Google Apps Script to send a draft. The following stand-alone script does that, and handles the necessary authorization.
Script
The full script is available in this gist.
/*
* Send all drafts labeled "send-tomorrow".
*/
function sendDayOldDrafts() {
var threads = GmailApp.search('in:draft label:send-tomorrow');
for (var i=0; i<threads.length; i++) {
var msgId = threads[0].getMessages()[0].getId();
sendDraftMsg( msgId );
}
}
/**
* Sends a draft message that matches the given message ID.
* Throws if unsuccessful.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/send.
*
* #param {String} messageId Immutable Gmail Message ID to send
*
* #returns {Object} Response object if successful, see
* https://developers.google.com/gmail/api/v1/reference/users/drafts/send#response
*/
function sendDraftMsg( msgId ) {
// Get draft message.
var draftMsg = getDraftMsg(msgId,"json");
if (!getDraftMsg(msgId)) throw new Error( "Unable to get draft with msgId '"+msgId+"'" );
// see https://developers.google.com/gmail/api/v1/reference/users/drafts/send
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts/send'
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
method: "post",
contentType: "application/json",
headers: headers,
muteHttpExceptions: true,
payload: JSON.stringify(draftMsg)
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
return JSON.parse(response.getContentText());
}
else {
// This is only needed when muteHttpExceptions == true
var err = JSON.parse(response.getContentText());
throw new Error( 'Error (' + result + ") " + err.error.message );
}
}
/**
* Gets the current user's draft messages.
* Throws if unsuccessful.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/list.
*
* #returns {Object[]} If successful, returns an array of
* Users.drafts resources.
*/
function getDrafts() {
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts';
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
headers: headers,
muteHttpExceptions: true
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
return JSON.parse(response.getContentText()).drafts;
}
else {
// This is only needed when muteHttpExceptions == true
var error = JSON.parse(response.getContentText());
throw new Error( 'Error (' + result + ") " + error.message );
}
}
/**
* Gets the draft message ID that corresponds to a given Gmail Message ID.
*
* #param {String} messageId Immutable Gmail Message ID to search for
*
* #returns {String} Immutable Gmail Draft ID, or null if not found
*/
function getDraftId( messageId ) {
if (messageId) {
var drafts = getDrafts();
for (var i=0; i<drafts.length; i++) {
if (drafts[i].message.id === messageId) {
return drafts[i].id;
}
}
}
// Didn't find the requested message
return null;
}
/**
* Gets the draft message content that corresponds to a given Gmail Message ID.
* Throws if unsuccessful.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/get.
*
* #param {String} messageId Immutable Gmail Message ID to search for
* #param {String} optFormat Optional format; "object" (default) or "json"
*
* #returns {Object or String} If successful, returns a Users.drafts resource.
*/
function getDraftMsg( messageId, optFormat ) {
var draftId = getDraftId( messageId );
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts'+"/"+draftId;
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
headers: headers,
muteHttpExceptions: true
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
if (optFormat && optFormat == "JSON") {
return response.getContentText();
}
else {
return JSON.parse(response.getContentText());
}
}
else {
// This is only needed when muteHttpExceptions == true
var error = JSON.parse(response.getContentText());
throw new Error( 'Error (' + result + ") " + error.message );
}
}
Authorization
To use Google's APIs, we need to have an OAuth2 token for the current user - just as we do for Advanced Services. This is done using ScriptApp.getOAuthToken().
After copying the code to your own script, open Resources -> Advanced Google Services, open the link for the Google Developers Console, and enable the Gmail API for your project.
As long as the script contains at least one GMailApp method that requires user authority, the authentication scope will be set properly for the OAuthToken. In this example, that's taken care of by GmailApp.search() in sendDayOldDrafts(); but for insurance you could include a non-reachable function call directly in the functions using the API.
I did it using the GmailMessage.forward method.
It works with upload images and attachments, but I had to set the subject to avoid the prefix "Fwd:", and the user name because it only displayed the user email to the recipients.
I didn't find a way to dispose the draft, so I just remove the label to prevent sending it again.
Script:
function getUserFullName(){
var email = Session.getActiveUser().getEmail();
var contact = ContactsApp.getContact(email);
return contact.getFullName();
}
function testSendTomorrow(){
var threads = GmailApp.search('in:draft label:send-tomorrow');
if(threads.length == 0){
return;
}
var labelSendTomorrow = GmailApp.getUserLabelByName("send-tomorrow");
for(var i = 0; i < threads.length; i++){
var messages = threads[i].getMessages();
for(var j = 0; j < messages.length; j++){
var mssg = messages[j];
if(mssg.isDraft()){
mssg.forward(mssg.getTo(), {
cc: mssg.getCc(),
bcc: mssg.getBcc(),
subject: mssg.getSubject(),
name: getUserFullName()
});
}
}
threads[i].removeLabel(labelSendTomorrow);
}
}
I'm new around here and don't have enough "reputation" to comment, so couldn't comment on Mogsdad's original answer so I'm having to create a new answer:
I've adapted Mogsdad's solution to also support replying/forwarding existing threads, not just brand new messages.
To use it on existing threads, you should first create the reply/forward, and only then label the thread. My code also supports several labels and setting up those labels.
I created a new gist for it, forking Mogsdad's, here: https://gist.github.com/hadasfester/81bfc5668cb7b666b4fd6eeb6db804c3
I still need to add some screenshot links in the doc but otherwise this is ready for use, and I've been using it myself. Hope you find it useful.
Also inlining it here:
/**
* This script allows you to mark threads/drafts with a predetermined label and have them get sent the next time your trigger
* sets off.
*
* Setup instructions:
* 1. Make a copy of this script (File -> Make a copy)
* 2. Follow the "Authorization" instructions on https://stackoverflow.com/a/27215474. (If later during setup/testing you get
* another permissions approval dialog, approve there as well).
* 2. I created two default labels, you can edit/add your own. See "TODO(user):" below. After that, to create them in gmail,
* choose "setUpLabel" function above and click the play button (TODO: screenshot). Refresh your gmail tab, you should see
* the new labels.
* 3. Click the clock icon above (TODO: screenshot) and set time triggers, e.g. like so: (TODO: screenshot)
* 4. I recommend also setting up error notifications: (TODO: screenshot).
*
* Testing setup:
* When you're first setting this up, if you want to test it, create a couple
* of drafts and label them. Then, in this
* script, select "sendWeekStartDrafts" or "sendTomorrowDrafts" in the function dropdown
* and press play. This manually triggers the script (instead of relying on the
* timer) so you can see how it works.
*
* Usage instructions:
* 1. To get a draft sent out on the next trigger, mark your draft with the label you chose.
* NOTE: If your draft is a reply to a thread, make sure you first create the draft and only then set the label on the
* thread, not the other way around.
* That's it! Upon trigger your draft will be sent and the label will get removed from the thread.
*
* Some credits and explanation of differences/improvements from other existing solutions:
* 1. This script was adapted from https://stackoverflow.com/a/27215474 to also support replying existing threads, not only
* sending brand new messages.
* 2. Other solutions I've run into are based on creating a new message, copying it field-by-field, and sending the new one,
* but those have many issues, some of which are that they also don't handle replies and forwards very elegantly.
*
* Enjoy!
**/
var TOMORROW_LABEL = '!send-tomorrow';
var WEEK_START_LABEL = '!send-week-start';
// TODO(user): add more labels here.
/**
* Set up the label for delayed send!
**/
function setUpLabels() {
GmailApp.createLabel(TOMORROW_LABEL);
GmailApp.createLabel(WEEK_START_LABEL);
// TODO(user): add more labels here.
}
function sendTomorrowDrafts() {
sendLabeledDrafts(TOMORROW_LABEL);
}
function sendWeekStartDrafts() {
sendLabeledDrafts(WEEK_START_LABEL);
}
// TODO(user): add more sendXDrafts() functions for your additional labels here.
/*
* Send all drafts labeled $MY_LABEL.
* #param {String} label The label for which to send drafts.
*/
function sendLabeledDrafts(label) {
var threads = GmailApp.search('in:draft label:' + label);
for (var i=0; i<threads.length; i++) {
var thread = threads[i];
var messages = thread.getMessages();
var success = false;
for (var j=messages.length-1; j>=0; j--) {
var msgId = messages[j].getId();
if (sendDraftMsg( msgId )) {
success = true;
}
}
if (!success) { throw Error( "Failed sending msg" ) };
if (success) {
var myLabel = GmailApp.getUserLabelByName(label);
thread.removeLabel(myLabel);
}
}
}
/**
* Sends a draft message that matches the given message ID.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/send.
*
* #param {String} messageId Immutable Gmail Message ID to send
*
* #returns {Object} Response object if successful, see
* https://developers.google.com/gmail/api/v1/reference/users/drafts/send#response
*/
function sendDraftMsg( msgId ) {
// Get draft message.
var draftMsg = getDraftMsg(msgId,"json");
if (!getDraftMsg(msgId)) return null;
// see https://developers.google.com/gmail/api/v1/reference/users/drafts/send
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts/send'
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
method: "post",
contentType: "application/json",
headers: headers,
muteHttpExceptions: true,
payload: JSON.stringify(draftMsg)
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
return JSON.parse(response.getContentText());
}
else {
// This is only needed when muteHttpExceptions == true
return null;
}
}
/**
* Gets the current user's draft messages.
* Throws if unsuccessful.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/list.
*
* #returns {Object[]} If successful, returns an array of
* Users.drafts resources.
*/
function getDrafts() {
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts';
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
headers: headers,
muteHttpExceptions: true
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
return JSON.parse(response.getContentText()).drafts;
}
else {
// This is only needed when muteHttpExceptions == true
var error = JSON.parse(response.getContentText());
throw new Error( 'Error (' + result + ") " + error.message );
}
}
/**
* Gets the draft message ID that corresponds to a given Gmail Message ID.
*
* #param {String} messageId Immutable Gmail Message ID to search for
*
* #returns {String} Immutable Gmail Draft ID, or null if not found
*/
function getDraftId( messageId ) {
if (messageId) {
var drafts = getDrafts();
for (var i=0; i<drafts.length; i++) {
if (drafts[i].message.id === messageId) {
return drafts[i].id;
}
}
}
// Didn't find the requested message
return null;
}
/**
* Gets the draft message content that corresponds to a given Gmail Message ID.
* See https://developers.google.com/gmail/api/v1/reference/users/drafts/get.
*
* #param {String} messageId Immutable Gmail Message ID to search for
* #param {String} optFormat Optional format; "object" (default) or "json"
*
* #returns {Object or String} If successful, returns a Users.drafts resource.
*/
function getDraftMsg( messageId, optFormat ) {
var draftId = getDraftId( messageId );
var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts'+"/"+draftId;
var headers = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
};
var params = {
headers: headers,
muteHttpExceptions: true
};
var check = UrlFetchApp.getRequest(url, params)
var response = UrlFetchApp.fetch(url, params);
var result = response.getResponseCode();
if (result == '200') { // OK
if (optFormat && optFormat == "JSON") {
return response.getContentText();
}
else {
return JSON.parse(response.getContentText());
}
}
else {
// This is only needed when muteHttpExceptions == true
return null;
}
}
You can search through all drafts and then send that specific draft no problem.
function sendMessage(id){
GmailApp.getDrafts().forEach(function (draft) {
mes = draft.getMessage()
if (mes.getId() == id) {
draft.send()
}
})
}
First, GmailDraft now has a send() function you can call directly. See: https://developers.google.com/apps-script/reference/gmail/gmail-draft#send()
Their code sample:
var draft = GmailApp.getDrafts()[0]; // The first draft message in the drafts folder
var msg = draft.send(); // Send it
Logger.log(msg.getDate()); // Should be approximately the current timestamp
Second, may not even need it now that google has released scheduled sending.
Click the arrow next to Send
Select your preferred time to send
A simpler alternative is to use the gmail api instead of gmailApp:
function sendtestDraft(draftId){
var request = Gmail.Users.Drafts.send({id : draftId},'me');
Logger.log(request);
}
above function example is used within a gs script at https://script.google.com.
It needs the draftId (not the message Id) and the draft will be sent. Images and attachments are all OK!
Info:https://developers.google.com/gmail/api/v1/reference/users/drafts/send

How to send XML request with Google Apps Script?

I'm trying to use Google Apps Script to retrieve data from Google Apps Reporting API which specify me to send XML request
My goal is to retrieve disk_space_report an put that data into spreadsheet so I can monitor the disk space in that spreadsheet and also process some data.
Anyone can give me an example on how to do that kind of stuff?
Thanks in advanced.
Here is a little code which I have written and using to fetch Google Apps users account report.
function startHere(){
var domain = UserManager.getDomain();
var fDate = '2012-12-18';//Utilities.formatDate(new Date(), Session.getTimeZone(), 'yyyy-MM-dd');
var url = 'https://www.google.com/hosted/services/v1.0/reports/ReportingData';
//Build API request parameters
var fetchArgs = googleOAuth_('Reporting', url);
fetchArgs.method = 'POST';
var rawXML = '<?xml version="1.0" encoding="UTF-8"?>'
+'<rest xmlns="google:accounts:rest:protocol" xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance ">'
+'<type>Report</type>'
+'<domain>'+domain+'</domain>'
+'<date>'+fDate+'</date>'
+'<page>1</page>'
+'<reportType>daily</reportType>'
+'<reportName>accounts</reportName>'
+'</rest>';
fetchArgs.payload = rawXML;
fetchArgs.contentType = "application/xml";
fetchArgs.headers = {"Content-type": "application/atom+xml charset=UTF-8"};
//Fetch CSV data
var csvData = UrlFetchApp.fetch(url, fetchArgs).getContentText();
//Parse CSV data and make a 2D array
var recs = csvData.split('\n');
var data = []; //this is actual 2D data
for(var i=0; i<recs.length-1; i++){
var temp = recs[i].split(',');
if(i==0) temp.push('percent_disk_usage');
else{
var usage = (parseInt(temp[5])*100)/(parseInt(temp[4])*1024*1024);
temp.push(usage);
}
data.push(temp);
}
//Write data to spreadsheet
}
function googleOAuth_(name,scope) {
var oAuthConfig = UrlFetchApp.addOAuthService(name);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey("anonymous");
oAuthConfig.setConsumerSecret("anonymous");
return {oAuthServiceName:name, oAuthUseToken:"always"};
}
Just a quick sample. Inside your script create a new HTML file called: xmlRequest with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<rest xmlns="google:accounts:rest:protocol"
xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance ">
<type>Report</type>
<domain>YOURDOMAIN.COM</domain>
<date>2012-12-01</date>
<page>1</page>
<reportType>daily</reportType>
<reportName>accounts</reportName>
</rest>
Change YOURDOMAIN.COM with your Google Apps domain. Inside Code.gs paste the following code:
/**
* Script configuration
*/
var SCOPE = 'https://www.google.com/hosted/services/v1.0/reports/ReportingData';
var APPNAME = "disk_space_report";
var URL = 'https://www.google.com/hosted/services/v1.0/reports/ReportingData';
function testit() {
// Generate the new entry from a template
var template = HtmlService.createHtmlOutputFromFile("xmlRequest").getContent();
var response = UrlFetchApp.fetch(URL,googleOAuth_('POST', template));
Logger.log(response.getContentText());
}
/**
* Google authentication loader
* #param {String} method the HTTP method to use for the UrlFetch operation, possible values are: GET, POST, PUT, DELETE
* #param {String} payload the payload to use if needed
* #return {Object} configuration options for UrlFetch, including oAuth parameters
*/
function googleOAuth_(method, payload) {
// Shared configuration for all methods
var oAuthConfig = UrlFetchApp.addOAuthService(APPNAME);
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope='+encodeURIComponent(SCOPE));
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey('anonymous');
oAuthConfig.setConsumerSecret('anonymous');
// Detect the required method
switch(method) {
case "GET":
return {oAuthServiceName:APPNAME, oAuthUseToken:'always'};
break;
case "POST":
return {oAuthServiceName:APPNAME, oAuthUseToken:'always', payload: payload, contentType: 'application/atom+xml', method: "POST"};
break;
case "PUT":
return {oAuthServiceName:APPNAME, oAuthUseToken:'always', payload: payload, contentType: 'application/atom+xml', method: "PUT"};
break;
case "DELETE":
return {oAuthServiceName:APPNAME, oAuthUseToken:'always', method: "DELETE"};
break;
default:
return {oAuthServiceName:APPNAME, oAuthUseToken:'always'};
break;
}
}
Now run the testit function and inside the logger you should get the raw disk usage stats to get parsed.