Trying to call a GAS function with Python from Cloud Shell Editor, but not sure what I'm doing wrong. I scoured through stackoverflow, and tried using executable API, but that forced me to use OAuth2.0 which I could never get it to work (tells me to authorize through a link then it 403's me). The function below at least lists the functions that I have on GAS, but the trying to run the function returns a 404.
from __future__ import print_function
# bot.py
import os
import pickle
import os.path
import google.auth
from google.oauth2 import service_account
from googleapiclient.discovery import build
# Replace 'your-service-account.json' with the path to your service account JSON file
service_account_json = 'service.json'
# Replace with your Google Apps Script project ID
script_id = 'UNIQUE-SCRIPTID'
# Define the scopes required for the Google Apps Script API
SCOPES = [
'https://www.googleapis.com/auth/script.projects',
'https://www.googleapis.com/auth/script.scriptapp'
]
def authorize_script_api(service_account_json):
# Load the service account credentials from the JSON file
creds = google.oauth2.service_account.Credentials.from_service_account_file(
service_account_json, scopes=SCOPES)
return creds
def list_functions(script_id, credentials):
try:
# Build the Google Apps Script API service
service = build('script', 'v1', credentials=credentials)
# Get the content of the specified Google Apps Script project
content = service.projects().getContent(scriptId=script_id).execute()
if 'files' in content:
for file in content['files']:
if 'functionSet' in file:
function_set = file['functionSet']
if 'values' in function_set:
for func in function_set['values']:
if 'name' in func:
print(func['name'])
except Exception as e:
print(f"An error occurred: {e}")
def run_function(script_id, credentials, function_name):
try:
# Build the Google Apps Script API service
service = build('script', 'v1', credentials=credentials)
# Create a request to run the specified function
request = {
"function": function_name
}
# Execute the function in the Google Apps Script project
response = service.scripts().run(scriptId=script_id, body=request).execute()
print(response)
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == '__main__':
# Authorize the script API
credentials = authorize_script_api(service_account_json)
# List functions in the Google Apps Script project
list_functions(script_id, credentials)
# Specify the name of the function you want to run
function_name = "helloworld"
# Run the specified function in the Google Apps Script project
run_function(script_id, credentials, function_name)
returns the following:
helloworld
setTrigger
scheduledTrigger
deleteTriggers
An error occurred: <HttpError 404 when requesting https://script.googleapis.com/v1/scripts/UNIQUE-SCRIPTID:run?alt=json returned "Requested entity was not found.". Details: "Requested entity was not found.">
This is what content
returns:
{'scriptId': 'UNIQUE-SCRIPTID', 'files': [{'name': 'appsscript', 'type': 'JSON', 'source': '{
"timeZone": "America/Los_Angeles",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"executionApi": {
"access": "MYSELF"
},
"oauthScopes": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.scriptapp"
]
}', 'lastModifyUser': {'photoUrl': 'redacted'}, 'createTime': '2022-09-06T01:19:20.404Z', 'updateTime': '2023-10-09T07:08:55.871Z', 'functionSet': {}}, {'name': 'Code', 'type': 'SERVER_JS', 'source': 'function helloworld() {
console.log("hello!");
}
function setTrigger() {
deleteTriggers();
var times = [[19,15]]; // 7:15PM
times.forEach(t_el => scheduledTrigger(t_el[0],t_el[1]));
}
function scheduledTrigger(hours,minutes) {
var today_D = new Date();
var year = today_D.getFullYear();
var month = today_D.getMonth();
var day = today_D.getDate();
pars = [year,month,day,hours,minutes];
var scheduled_D = new Date(...pars);
var hours_remain=Math.abs(scheduled_D - today_D) / 36e5;
ScriptApp.newTrigger("Announcement")
.timeBased()
.after(hours_remain * 60 *60 * 1000)
.create()
}
function deleteTriggers() {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if ( triggers[i].getHandlerFunction() == "Announcement") {
ScriptApp.deleteTrigger(triggers[i]);
}
}
}
}', 'lastModifyUser': {'photoUrl': 'redacted'}, 'createTime': '2022-09-06T01:19:20.397Z', 'updateTime': '2023-10-09T07:08:55.871Z', 'functionSet': {'values': [{'name': 'helloworld'}, {'name': 'setTrigger'}, {'name': 'scheduledTrigger', 'parameters': ['hours', 'minutes']}, {'name': 'deleteTriggers'}]}}]}
When I saw your showing script, I was worried that the reason for your current issue might be due to Warning: The Apps Script API does not work with service accounts.
. Ref And, when I tested your situation using the service account, in the current stage, it seems that the script can be retrieved using the service account. However, it seems that the script cannot be run using the service account, and I confirmed the same error message with you. I'm worried that this might be the reason for your current issue.
In order to achieve your goal, in that case, as a workaround, how about executing your Google Apps Script using Web Apps? I think that when Web Apps is used, the Google Apps Script can be run with the service account. The flow of this workaround is as follows.
Please share your Google Apps Script with the email of the service account. If you have already done this, it is not required to run this.
Please add the following function to your Google Apps Script.
function doGet(e) {
const { functionName } = e.parameter;
return ContentService.createTextOutput((functionName && this[functionName]) ? this[functionName]() : "No function.");
}
The detailed information can be seen in the official document.
Please set this using the new IDE of the script editor.
https://script.google.com/macros/s/###/exec
. This URL is used with Python script.Please set your Web Apps URL to url = "https://script.google.com/macros/s/###/exec" # Please set your Web Apps URL.
.
from __future__ import print_function
import requests
import google.auth
from google.oauth2 import service_account
from googleapiclient.discovery import build
# Replace 'your-service-account.json' with the path to your service account JSON file
service_account_json = 'service.json'
# Replace with your Google Apps Script project ID
script_id = 'UNIQUE-SCRIPTID'
# Define the scopes required for the Google Apps Script API
SCOPES = [
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.scriptapp",
"https://www.googleapis.com/auth/drive.readonly" # Added. This is used for requesting Web Apps.
]
def authorize_script_api(service_account_json):
creds = google.oauth2.service_account.Credentials.from_service_account_file(service_account_json, scopes=SCOPES)
return creds
def list_functions(script_id, credentials):
try:
service = build("script", "v1", credentials=credentials)
content = service.projects().getContent(scriptId=script_id).execute()
if "files" in content:
for file in content["files"]:
if "functionSet" in file:
function_set = file["functionSet"]
if "values" in function_set:
for func in function_set["values"]:
if "name" in func:
print(func["name"])
except Exception as e:
print(f"An error occurred: {e}")
def run_function(credentials, function_name):
try:
url = "https://script.google.com/macros/s/###/exec" # Please set your Web Apps URL.
access_token = credentials.token
url += "?functionName=" + function_name
res = requests.get(url, headers={"Authorization": "Bearer " + access_token})
print(res.text)
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
# Authorize the script API
credentials = authorize_script_api(service_account_json)
# List functions in the Google Apps Script project
list_functions(script_id, credentials)
# Specify the name of the function you want to run
function_name = "helloworld"
# Run the specified function in the Google Apps Script project
run_function(credentials, function_name)
When I tested this modified script, I could retrieve the function names from list_functions
. And also, I could retrieve the response value from run_function
.
When you modify the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the details of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".