Pushing Slack data to Google Sheets w/ Event Subscription - google-apps-script

I'm trying to create a bot on Slack that sends the data of new messages sent to a private channel (that the bot is in) to a Google Sheet. I was successfully able to do this with data following a Slack slash command, by using this script:
function doPost(e) {
if (typeof e !== 'undefined') {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
sheet.getRange(1,1).setValue(JSON.stringify(e));
}
}
I tried using the same script with the Events API, but it needs to pass a request with a challenge parameter, and the endpoint must respond with the challenge value. Using the GScript web app's URL, I keep getting a failed response. How do I have the URL Verification Handshake work with Google Sheets and respond with the correct challenge string?
HTTP Post Fail

How about this modification?
The official document says as follows. This has already been mentioned in your question.
challenge: a randomly generated string produced by Slack. The point of this little game of cat and mouse is that you're going to respond to this request with a response body containing this value.
So please modify your script for returning the value of challenge from doPost as follows.
Modified script:
function doPost(e) {
if (typeof e !== 'undefined') {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
sheet.getRange(1,1).setValue(JSON.stringify(e));
}
var v = JSON.parse(e.postData.contents); // Added
return ContentService.createTextOutput(v.challenge); // Added
}
Note:
When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to Web Apps. Please be careful this.
References:
Subscribing to event types
createTextOutput(content)
Added:
From your replying of I'm getting the same error., I thought that the latest script might not be reflected to Web Apps. And as a simple script, please copy and paste the following script instead of your script. And please redeploy the Web Apps as new version. Then, please test it again.
Sample script:
function doPost(e) {
var v = JSON.parse(e.postData.contents);
return ContentService.createTextOutput(v.challenge);
}

Related

Google Apps Script XMLHttpRequest not showing parameters

I am able to make the POST request to my google apps script web app, but I can't access my e.parameters when I log them.
HTML CODE:
<form onsubmit="submitForm(event)">
<input type="text" name="fname" required>
<button type="submit">Submit</button>
</form>
JS CODE:
function submitForm(e){
e.preventDefault()
var url = "https://xxxxxxxxxxxxxxxxxxxxxx/exec"
var params = "employeeStatus='Active'&name='Henry'";
var xhr = new XMLHttpRequest()
xhr.open("POST",url,true)
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")
xhr.onreadystatechange = ()=>{
var readyState = xhr.readyState
var status = xhr.status
if(readyState == 4 && status == 200){
var response = JSON.parse(xhr.responseText)
console.log(response)
}
}
xhr.send(params)
}
APPS SCRIPT CODE:
function doPost(e){
var values = e.parameters;
Logger.log(values)
return ContentService.createTextOutput(JSON.stringify({"a":5,"b":2}))
}
Can anyone please tell me what I'm doing wrong here? I've iterated the apps script code to try and log the e.parameters but I'm unable to get anything to work.
***NOTES:
I'm aware that the "params" value is NOT the same as the form input value
I return the JSON string just to ensure that the code is running all the way through and I can practice JSON.parse/JSON.stringify on the client-side.
When I saw your script, I think that your value of e.parameters is {"employeeStatus":["'Active'"],"name":["'Henry'"]}.
About I've iterated the apps script code to try and log the e.parameters but I'm unable to get anything to work., I think that the reason for your issue is due to that your request of "XMLHttpRequest" include no access token. From your request, I thought that the settings of Web Apps might be Execute as: Me and Who has access to the app: Anyone with V8 runtime. If my understanding is correct, the reason for your issue is due to that.
If you want to show Logger.log(values) in the log, please include the access token to the request header as follows.
Modified script:
function submitForm(e) {
e.preventDefault();
var url = "https://script.google.com/macros/s/###/exec"; // Your web apps URL.
url += "?access_token=###your access token###";
var params = "employeeStatus=Active&name=Henry";
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = () => {
var readyState = xhr.readyState;
var status = xhr.status;
if (readyState == 4 && status == 200) {
var response = JSON.parse(xhr.responseText);
console.log(response);
}
}
xhr.send(params);
}
In this case, as a test, you can simply retrieve your access token by the following Google Apps Script. // DriveApp.getFiles() is put for automatically detecting the scope of Drive API for accessing Web Apps. The expiration time of this access token is 1 hour. Please be careful about this. When the expiry time is over, please retrieve the access token again.
const sample = _ => {
console.log(ScriptApp.getOAuthToken());
// DriveApp.getFiles()
}
When the above script is run, the following value is shown in the log by Logger.log(values).
{access_token=[###], employeeStatus=[Active], name=[Henry]} : This is due to Logger.log.
When console.log(values) is used, { access_token: [ '###' ], employeeStatus: [ 'Active' ], name: [ 'Henry' ]} is shown.
Note:
As another approach, for example, when you want to check the value of e of doPost, I think that you can store the value in a Spreadsheet as a log as follows. By this, when doPost is run, the value of e is stored in the Spreadsheet as a log. In this case, the access token is not required to be used.
function doPost(e) {
SpreadsheetApp.openById("###spreadsheetId###").getSheets()[0].appendRow([new Date(), JSON.stringify(e)]);
return ContentService.createTextOutput(JSON.stringify({ "a": 5, "b": 2 }))
}
Note
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
Reference:
Taking advantage of Web Apps with Google Apps Script

If LockService works for a common script for multiple users, is there an option when dealing with multiple scripts sending data to the same column?

I have 4 different scripts linked to 4 different accounts, which do their functions and in the end send a value from a specific cell to a final spreadsheet and in that final spreadsheet , to the same column of data:
Script Spreadsheet 1:
function Spreadsheet1() {
HIDDEN CODE LINES → THEY ARE COLLECT DATA FROM AN API
var first_sheet_id = SpreadsheetApp.openById('SPREADSHEET 1');
var first_sheet_page = first_sheet.getSheetByName('Sheet86');
var second_sheet_id = SpreadsheetApp.openById('SAME DESTINATION AS FOUR SCRIPTS');
var second_sheet_page = second_sheet.getSheetByName('Sheet86');
var r=1;
while(second_sheet_page.getRange(r,1).getValue()) {
r++;
}
second_sheet_page.getRange(r,1).setValue(first_sheet_page.getRange(1,1).getValue());
}
Script Spreadsheet 2:
function Spreadsheet2() {
HIDDEN CODE LINES → THEY ARE COLLECT DATA FROM AN API
var first_sheet_id = SpreadsheetApp.openById('SPREADSHEET 2');
var first_sheet_page = first_sheet.getSheetByName('Sheet86');
var second_sheet_id = SpreadsheetApp.openById('SAME DESTINATION AS FOUR SCRIPTS');
var second_sheet_page = second_sheet.getSheetByName('Sheet86');
var r=1;
while(second_sheet_page.getRange(r,1).getValue()) {
r++;
}
second_sheet_page.getRange(r,1).setValue(first_sheet_page.getRange(1,1).getValue());
}
Script Spreadsheet 3:
function Spreadsheet3() {
HIDDEN CODE LINES → THEY ARE COLLECT DATA FROM AN API
var first_sheet_id = SpreadsheetApp.openById('SPREADSHEET 3');
var first_sheet_page = first_sheet.getSheetByName('Sheet86');
var second_sheet_id = SpreadsheetApp.openById('SAME DESTINATION AS FOUR SCRIPTS');
var second_sheet_page = second_sheet.getSheetByName('Sheet86');
var r=1;
while(second_sheet_page.getRange(r,1).getValue()) {
r++;
}
second_sheet_page.getRange(r,1).setValue(first_sheet_page.getRange(1,1).getValue());
}
Script Spreadsheet 4:
function Spreadsheet4() {
HIDDEN CODE LINES → THEY ARE COLLECT DATA FROM AN API
var first_sheet_id = SpreadsheetApp.openById('SPREADSHEET 4');
var first_sheet_page = first_sheet.getSheetByName('Sheet86');
var second_sheet_id = SpreadsheetApp.openById('SAME DESTINATION AS FOUR SCRIPTS');
var second_sheet_page = second_sheet.getSheetByName('Sheet86');
var r=1;
while(second_sheet_page.getRange(r,1).getValue()) {
r++;
}
second_sheet_page.getRange(r,1).setValue(first_sheet_page.getRange(1,1).getValue());
}
LockService would work if it was the same script with multiple users trying to use it at the same time.
But in my case, there are four scripts with auto trigger (every 5 minutes) running and sending to the same column of the same spreadsheet.
Is there any way to be able to avoid having the risk of them meeting and putting values on the same lines?
If there is any way, please create a visual example of how to use it
so that I understand the method as I believe it is not as simple as my
knowledge limit.
From your updated question, in your situation, how about using Web Apps with LockService? From your question, I confirmed that 4 accesses are run simultaneously. In this case, in my benchmark for writing a Spreadsheet using Web Apps, it has already been found that 4 workers can be used. Ref From this result, I proposed to use Web Apps as a workaround for achieving your goal.
Usage:
1. Prepare script for Web Apps.
As a sample, please copy and paste the following script to a new Google Apps Script project. This script is used as Web Apps. In this case, you can also put this script in one of 4 scripts. But as a sample, I separated 4 clients and a server of Web Apps.
Please set your destination Spreadsheet ID.
function doGet(e) {
const lock = LockService.getDocumentLock();
if (lock.tryLock(350000)) {
try {
var ssId = e.parameter.spreadsheetId;
if (!ssId) return ContentService.createTextOutput("No spreadsheet ID.");
var first_sheet = SpreadsheetApp.openById(ssId);
var first_sheet_page = first_sheet.getSheetByName('Sheet86');
var second_sheet = SpreadsheetApp.openById('SAME DESTINATION AS FOUR SCRIPTS'); // <--- Please set your destination Spreadsheet ID.
var second_sheet_page = second_sheet.getSheetByName('Sheet86');
var r = 1;
while (second_sheet_page.getRange(r, 1).getValue()) {
r++;
}
second_sheet_page.getRange(r, 1).setValue(first_sheet_page.getRange(1, 1).getValue());
} catch (e) {
return ContentService.createTextOutput(e.message);
} finally {
lock.releaseLock();
return ContentService.createTextOutput("Done");
}
} else {
return ContentService.createTextOutput("Timeout");
}
}
2. Deploy Web Apps.
The detailed information can be seen at the official document.
On the script editor, at the top right of the script editor, please click "click Deploy" -> "New deployment".
Please click "Select type" -> "Web App".
Please input the information about the Web App in the fields under "Deployment configuration".
Please select "Me" for "Execute as".
This is the importance of this workaround.
Please select "Anyone" for "Who has access".
In your situation, I thought that this setting might be suitable.
Of course, you can use the access token. If you want to use the access token, please set it as Anyone with Google account and use the access token at the client side.
Please click "Deploy" button.
Copy the URL of the Web App. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
3. Prepare script of 4 clients.
Spreadsheet1
Please copy and paste the following script to the script editor of Spreadsheet 1. And, please reinstall the trigger. Because the scope is authorized.
function Spreadsheet1() {
const srcSpreadsheetId = 'SPREADSHEET 1'; // Please set spreadsheet ID.
const webAppsUrl = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL.
const res = UrlFetchApp.fetch(webAppsUrl + "?spreadsheetId=" + srcSpreadsheetId);
console.log(res.getContentText());
}
Spreadsheet2
Please copy and paste the following script to the script editor of Spreadsheet 2. And, please reinstall the trigger. Because the scope is authorized.
function Spreadsheet2() {
const srcSpreadsheetId = 'SPREADSHEET 2'; // Please set spreadsheet ID.
const webAppsUrl = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL.
const res = UrlFetchApp.fetch(webAppsUrl + "?spreadsheetId=" + srcSpreadsheetId);
console.log(res.getContentText());
}
Spreadsheet3
Please copy and paste the following script to the script editor of Spreadsheet 3. And, please reinstall the trigger. Because the scope is authorized.
function Spreadsheet3() {
const srcSpreadsheetId = 'SPREADSHEET 3'; // Please set spreadsheet ID.
const webAppsUrl = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL.
const res = UrlFetchApp.fetch(webAppsUrl + "?spreadsheetId=" + srcSpreadsheetId);
console.log(res.getContentText());
}
Spreadsheet4
Please copy and paste the following script to the script editor of Spreadsheet 4. And, please reinstall the trigger. Because the scope is authorized.
function Spreadsheet4() {
const srcSpreadsheetId = 'SPREADSHEET 4'; // Please set spreadsheet ID.
const webAppsUrl = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL.
const res = UrlFetchApp.fetch(webAppsUrl + "?spreadsheetId=" + srcSpreadsheetId);
console.log(res.getContentText());
}
4. Testing.
After the script of Web Apps and the scripts of 4 clients were prepared, please run those functions of clients. By this, the script of Web Apps can be run with LockService. In this case, your 4 clients can be run simultaneously.
Note:
In this case, the order of the functions Spreadsheet1 to Spreadsheet4 cannot be controlled. Please be careful about this.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
References:
Benchmark: Concurrent Writing to Google Spreadsheet using Form
Web Apps
Taking advantage of Web Apps with Google Apps Script

How to handle authorization for Apps Script from Chrome extension

I try to execute a Google Apps Script from my Chrome extension. My Google Apps Script uses Spreadsheet Service - it reads data from the user's spreadsheet, url of which is provided by user to my extension and then sent by extension to GAS. So, I've deployed Google Apps Script as web app, which is executed by a user accessing the web app and can be accessed by anyone. The problem is that when the script is run for the first time by a new user, the user can't see the authorization request so the script can't access the spreadsheet and send data to the extension. Everything works fine only if a user runs the script manually in the browser - only then the authorization request appears.
My question is how extension can trigger the authorization request to appear automatically?
I have read this question with answers Running Apps Script from Chrome extension requires authorization but there are only instructions on how to avoid authorization, which in my case can't be avoided. I would appreciate any help with this.
Update: I'm trying to use getAuthorizationUrl() as advised in the comments and now I'm stuck again. Here is the code from the Google Apps Script:
function doGet(e) {
var url=e.parameter.url;
var ss = SpreadsheetApp.openByUrl(url);
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
var status=authInfo.getAuthorizationStatus();
if (status=="NOT_REQUIRED") {
var sheet = ss.getSheets()[0];
var data = getData(sheet);
if(!data) {
data = '';
}
return ContentService.createTextOutput(JSON.stringify({'result': data})).setMimeType(ContentService.MimeType.JSON);
} else {
var authUrl=authInfo.getAuthorizationUrl();
return authUrl;
}
}
In both cases responseType of my XMLHttpRequest will be "", so how can my extension distinguish between 2 possible responses? I need something like this in the content script (don't know what to put inside if):
xhr.onreadystatechange = function(response) {
if (xhr.readyState === 4 && xhr.status == 200) {
try {
if (??response is authUrl) {
//do redirect;
}
else {
//process the data from the spreadsheet
}

Calling Google Apps Script in another Project File

I am trying to call a Google Apps Script file that is in another project file following the sample here using UrlFetchApp.fetch.
I'm getting the same error that the original poster mentions but I am not having an success with my sample.
Did Google change something in the last 4 years that prevents me from calling the other script file?
See script below.
Below is the function that I am using to call the other project file
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/***/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
Below is the function that I am trying to call. Additionally, I have ran the authorizeDrive function and published as a webapp.
function authorizeDrive()
{
var forScope = DriveApp.getRootFolder();
}
function doPost()
{
var ss = SpreadsheetApp.openById('ssID');
var name = ss.getName();
Logger.log('called');
return ContentService.createTextOutput(name);
}
You want to run the Google Apps Script in the GAS project A by accessing to Web Apps from the GAS project B.
In your case, Web Apps is deployed by Who has access to the app: of Only myself or Anyone.
You want to access to Web Apps using the access token.
The GAS project A and B are in your Google Drive.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
I think that in your case, the scope is required to be added to the project including makeRequest(). So in order to add the scope for accessing to Web Apps using the access token, how about the following modification?
Modified script:
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/***/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
// DriveApp.getFiles() // This comment line is used for automatically detecting the scope.
Please add the // DriveApp.getFiles() of the comment line. This comment line is used for automatically detecting the scope.
In this case, https://www.googleapis.com/auth/drive.readonly is added to the scopes. If this didn't resolve your issue, please add the comment line of // DriveApp.createFile(blob). In this case, https://www.googleapis.com/auth/drive is added.
Note:
When the script of Web Apps side is modified, please redeploy it as new version. By this, the latest script is reflected to Web Apps. Please be careful this.
If the owner of GAS project of Web Apps is not your account which has the script of makeRequest(), at first, please share the GAS project file of Web Apps with your account. Then, please test it. This specification has added at April 11, 2018. Also, please be careful this.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
If I misunderstood your question and this was not the result you want, I apologize.

Calling a bound script's method using the Execution API

I'm using PropertiesServices as variables, specifically Document Properties , in order to replace some tokens like "{client name}". Since those properties are scoped to the bound script only, I'm looking for a way to modify their values from my PHP application.
Is it possible to call a bound script's function using the Execution API, or maybe from a standalone script? Otherwise, should I instead use the Script Properties instead (although the docs make me think you can't use them if the script isn't 'standalone).
It looks like if the user that the Execution API is running under has permission to the doc that bound script ran by the execution api can read document properties.
Here is my test:
Create a new spreadsheet. Create a new script. Add some data using the menu from onOpen. Run executeAPI inside the script. The log successfully shows the document properties.
function onOpen() {
var testMenu = SpreadsheetApp.getUi().createMenu("test")
testMenu.addItem("Add some data", "addData").addToUi();
testMenu.addItem("Preview data", "getData").addToUi();
}
function getData(){
var keys = PropertiesService.getDocumentProperties().getKeys();
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().clear().appendRow(keys)
}
function returnData(){
return PropertiesService.getDocumentProperties().getKeys();
}
function addData(){
var DT = new Date().toString()
PropertiesService.getDocumentProperties().setProperty(DT,DT);
}
function executeAPI(){
var url = 'https://script.googleapis.com/v1/scripts/'+ScriptApp.getProjectKey()+':run';
var payload = JSON.stringify({"function": "returnData","parameters":[], "devMode": true});
var params={method:"POST",
headers:{Authorization: "Bearer "+ ScriptApp.getOAuthToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
Logger.log(results)
}