I'm attempting to user Google API's via PHP to retrieve calendar events. My Environment
Windows: 10
PHP: 7.0
Apache: 2.4
Google API's loaded via command: composer require google/apiclient:"^2.0"
I'm using the following as test code (test2.php):
<html>
<head>
<title>PHP Test</title>
</head>
<body>
This is a line of text in test2.php
<?php
require_once 'C:\PHP\vendor\autoload.php';
$client = new Google_Client();
// $credentialsJson should contain the path to the json file downloaded from the API site
$credentialsJson = 'C:\Apache24\htdocs\credentials.json';
$credentials = $client->loadServiceAccountJson(
$credentialJson,
'https://www.googleapis.com/auth/calendar'
);
$client->setAssertionCredentials($credentials);
$service = new Google_Service_Calendar($client);
// $calendarId should contain the calendar id found on the settings page of the calendar
$calendarId = '*****myCalendarID*****';
$events = $service->events->listEvents($calendarId);
echo $events;
?>
</body>
</html>
I place the test2.php file in C:\Apache24\htdocs.
When I browse to: localhost/test2.php the browser displays:
This is a line of text in test2.php
Fatal error: Uncaught Error: Call to undefined method Google_Client::loadServiceAccountJson() in C:\Apache24\htdocs\test2.php:14 Stack trace: #0 {main} thrown in C:\Apache24\htdocs\test2.php on line 14
Why would this not find the method "loadServiceAccountJson"?
Thanks for looking at this.
I am not sure what tutorial you are following. this is the code i use.
ServiceAccount.php
require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
return getServiceAccountClient();
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
* Scopes will need to be changed depending upon the API's being accessed.
* array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
Note: A service account is not you using this will be requesting data from the service accounts google calendar account. if you want it to have access to your personal calendar your going to have to take the service account email address and share your calendar with it like you would any other user.
Related
It's been such a major headache getting my service account to authenticate on the same webapp where I have users logging in via oauth2 as well.
So I'm wondering, is this even possible?
If not, should one just stick with the service account? Does one have to then authenticate the users on one's own - old school style? Haha
Thanks.
Regarding the service account, I have enabled the domain wide delegation, enabled the client key + api scope in my G suite admin console, and have gotten the php sample with the books api working. However any time I try any other api, other than books, I get the error,
client is unauthorized to retrieve access tokens using this method
UPDATE: I've tried to use #dalmto's example, and have added a few lines to test the gmail api, for example:
putenv('GOOGLE_APPLICATION_CREDENTIALS=credentials.json');
$user = 'email#domain.de';
function getGoogleClient() {
return getServiceAccountClient();
}
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client2 = new Google_Client();
$client2->useApplicationDefaultCredentials();
$client2->setScopes(array('https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/admin.directory.user.readonly','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/calendar'));
$client2->setAccessType('offline');
$client2->setSubject($user);
return $client2;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
$newGoogleClient = getGoogleClient();
$service3 = new Google_Service_Gmail($newGoogleClient);
$results3 = $service3->users_labels->listUsersLabels($user);
But am now just receiving "400: Bad Request" errors
EDIT: After some more digging there is a note: 'failedPrecondition' - any idea which precondition that could be? I've allowed the following scopes for the client in my admin console:
hxxps://www.googleapis.com/auth/gmail.metadata,
hxxps://www.googleapis.com/auth/userinfo.email,
hxxps://www.googleapis.com/auth/userinfo.profile,
hxxps://www.googleapis.com/auth/gmail.modify,
hxxps://www.googleapis.com/auth/gmail.readonly,
hxxps://www.googleapis.com/auth/gmail.labels,
hxxps://mail.google.com/
And enabled the apis and enabled the scope in the 'OAuth Consent Screen'
DWD is also enabled: Service Account Overview Screenshot
EDIT2: Okay so I found the missing precondition was the "setSubject".
Once I added that it went a step further, but still failed again at '"error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method.'
FYI: When creating the service account, I gave it the "project -> owner" role. Is that sufficient? Does one have to add more?
EDIT3: I've also just checked logger and it says that DWD is enabled.. Im at my whits end here haha
client: {
adminState: {
updateTime: "2018-11-23T00:29:44.810Z"
}
assertionMatchExistingGrant: "MATCH_GRANT_DISABLED"
authType: "PUBLIC_KEY"
brandId: "aaaaaaaaaaaaaa"
clientId: "aaaaaaaaaaaaaaaaaa"
consistencyToken: "2018-11-23T00:29:44.953175Z"
creationTime: "2018-11-23T00:29:44.810Z"
displayName: "Client for servicemaint1"
domainWideDelegation: "DELEGATION_ENABLED"
projectNumber: "aaaaaaaaaaaaaaaa"
threeLeggedOauth: "DISABLED"
updateTime: "2018-11-23T00:29:44.953175Z"
}
EDIT4: FINALLY WORKING!
So I had been trying this in a new project I created for testing all morning / last night. But my oauth2 user authenticating was running through a different project (where I also couldn't get the service account working all of yesterday morning / afternoon).
So anyway, I noticed in: https://myaccount.google.com/permissions "Apps with Access to your account" - only my old project / app was authorized. So I switched back to my first project, created a new service account client ID .json file and it finallyyy worked to authenticate both! :)
I must have that authorized that somewhere extra along the line which I had not done with the second project.
Thanks again.
EDIT5: One more quick question - is this the correct way to do this on stackoverflow? With constantly going back to edit?
Also for others stumbling upon this later, here's my total authentication block (sorry its a bit long):
putenv('GOOGLE_APPLICATION_CREDENTIALS=maintenanceapp.json');
$user = 'xyz#abc.com';
function getGoogleClient() {
return getServiceAccountClient();
}
function getServiceAccountClient() {
$user = 'xyz#abc.com';
try {
// Create and configure a new client object.
$client2 = new Google_Client();
$client2->useApplicationDefaultCredentials();
$client2->setScopes(['https://www.googleapis.com/auth/gmail.metadata','https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.modify','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.labels']);
//$client2->setAccessType('offline');
$client2->setSubject($user);
return $client2;
} catch (Exception $e) {
echo "An error occurred: " . $e->getMessage();
}
}
$newGoogleClient = getGoogleClient();
$service3 = new Google_Service_Gmail($newGoogleClient);
$results3 = $service3->users_labels->listUsersLabels($user);
/*************************************************
* Ensure you've downloaded your oauth credentials
************************************************/
if (!$oauth_credentials = getOAuthCredentialsFile()) {
echo missingOAuth2CredentialsWarning();
return;
}
/************************************************
* NOTICE:
* The redirect URI is to the current page, e.g:
* http://localhost:8080/idtoken.php
************************************************/
$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
$client = new Google_Client();
// USER AUTH
$client->setAuthConfig($oauth_credentials);
$client->setRedirectUri($redirect_uri);
$client->setScopes(array('https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/calendar'));
$client->setApprovalPrompt('auto');
$client->setAccessType('offline');
$plus = new Google_Service_Plus($client);
/************************************************
* If we're logging out we just need to clear our
* local access token in this case
************************************************/
if (isset($_REQUEST['logout'])) {
unset($_SESSION['id_token_token']);
}
/************************************************
* If we have a code back from the OAuth 2.0 flow,
* we need to exchange that with the
* Google_Client::fetchAccessTokenWithAuthCode()
* function. We store the resultant access token
* bundle in the session, and redirect to ourself.
************************************************/
if (isset($_GET['code'])) {
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
// store in the session also
$_SESSION['id_token_token'] = $token;
// redirect back to the example
header('Location: https://abc.de/index.php');
// return;
}
/************************************************
If we have an access token, we can make
requests, else we generate an authentication URL.
************************************************/
if (
!empty($_SESSION['id_token_token'])
&& isset($_SESSION['id_token_token']['id_token'])
) {
$client->setAccessToken($_SESSION['id_token_token']);
} else {
$authUrl = $client->createAuthUrl();
//header('Location: ' . $authUrl);
}
/************************************************
If we're signed in we can go ahead and retrieve
the ID token, which is part of the bundle of
data that is exchange in the authenticate step
- we only need to do a network call if we have
to retrieve the Google certificate to verify it,
and that can be cached.
************************************************/
if ($client->getAccessToken()) {
$token_data = $client->verifyIdToken();
}
In google developer console when you create your project and the credentials you must choose which type of client you are going to create for which type of application.
There are several different ways to authenticate to google.
OAuth2 native
OAuth2 web
Mobile
Service account
The code to use these clients is also different. You cant create a web OAuth2 client and use it for the code meant to be calling a service account.
"client is unauthorized to retrieve access tokens using this method".
Means exactly that. The client you have set up on Google developer console is either not a service account client or the code you are using is not meant for a service account client.
This is my serviceaccount.php sample. If your code needs to look something like this and you need to make sure that the client you created on the google developer console is a service account client.
require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
return getServiceAccountClient();
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
* Scopes will need to be changed depending upon the API's being accessed.
* array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
Developer console
Under clients check that the client you are using is one that can be found under service account keys. If not then it is the wrong client type and will not work with your code. Create a new service account client and set up domain wide delegation with that client id.
response_type=code
client_id=348268306866-9dl0kdgn2f9bjhoge7pris1jo8u9si47.apps.googleusercontent.com
redirect_uri=https://degoo.com/me/googleoauth2callback
access_type=offline
scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/contacts.readonly
state={"RedirectUrl":"/me/chooseaccount","RegisterIfNotExists":true}
That’s all we know.
I am using this library the Google php client library and following the google drive quickstart tutorial.
$client = new Google_Client();
$client->setApplicationName('Google Drive API PHP Quickstart');
$client->setScopes(Google_Service_Drive::DRIVE_METADATA_READONLY);
$client->setAuthConfig('credentials.json');
$client->setAccessType('offline');
I am trying to upload my files from my PHP application to Google Drive. When I run the application to upload, it is asking for authorization. To do this its requiring that i login to my google account and consent the access to my drive account before coming back to my application.
I want to skip this step. How do i upload to google drive without requiring authorization to Google.
What you need to understand first is that the diffrence between private and public data. Private data is owned by a user and public data is public anyone can access it.
Google drive data is private user data. In order to access it you must have the permission of the user who owns it. The most common way of getting that permission is using Oauth2 and popping up the consent screen that you are seeing in the code you have now. There is another option though.
If the account you are accessing is your own one that you as the developer have control over then you can use a service account. Service accounts are used for server to server authentication. Service accounts are preauthorized. What you will do is take the service account email address and share a directory or a file on your google drive account that you want it to have access to. Once you have granted it access it will have access it will not need to login.
require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
return getServiceAccountClient();
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
* Scopes will need to be changed depending upon the API's being accessed.
* array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
my sample on service accounts. serviceaccount.php
I am trying to get my web application to use a dedicated email account to upload files to google drive through the API. The problem being that it keeps requiring the potential users to authenticate the account that I prepared for it, which is exactly what I am trying to prevent the need for.
Any solutions I came across directed me at using the refresh token to ensure the account stays authorized. This still comes with the issue that on the first time use, an user has to authenticate said account. Saving the access token with the refresh token locally didn't work, as it still forced new users to authenticate.
the snippet below displays the code used to authenticate.
class AccessDrive
{
private $client;
//initialize the client data provided on the call of the function
private function initClient($scopes, $clientsecret, $returnUrl)
{
$client = new Google_Client();
try{$client->setAuthConfig($clientsecret);}catch(Google_Exception $e){echo $e;}
$client->setClientId(CLIENT_ID);
$client->setApplicationName(APPLICATION_NAME);
$client->setAccessType('offline');
//$client->setApprovalPrompt('force');
$client->setIncludeGrantedScopes(true); // incremental auth
foreach ($scopes as $scope) {
$client->addScope($this->getScope($scope)); //assume one or multiple from the google drive scopes
}
$client->setRedirectUri( $returnUrl);
return $client;
}
public function Login()
{
if (!$_SESSION['code']) {
$auth_url = $this->client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
error_reporting(-1 & ~E_WARNING);
$this->client->authenticate($_SESSION['code']);
}
return false;
}
public function __construct($returnUrl, $scopes, $clientSecret)
{
$this->client = $this->initClient($scopes, $clientSecret, $returnUrl);
$this->Login();
}
}
?>
How does one make sure that the application will not need to prompt users to authenticate, staying authenticated outside the functionality the user is supposed to access?
--> Edit:
After attempting to implement the suggested method, by DaImTo, the following code generates a Google_Service_Exception, stating that the client is unauthorized to retrieve access tokens with this method. I am not sure where that goes wrong.
class AccessDrive
{
private $client;
public function __construct( $scopes, $credentials, $email, $toImpersonate )
{
putenv( "GOOGLE_APPLICATION_CREDENTIALS=".$credentials);
$scopelist =[];
foreach ($scopes as $scope) {
array_push($scopelist, $this->getScope($scope));
}
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope($scopelist);
$client->setSubject($toImpersonate);
$this->client = $client;
}
--> Edit:
If you downvote, I would like to know why so I can improve my current and future questions.
You are currently authencating using Oauth2. Oauth2 allows you to request access from a user to access their google drive account. Assuming they grant you access you are given an access token which allows you to access their data for an hour. If you request offline access you will be given a refresh token which you can use to request a new access token when ever the access token expires.
In your case becouse you are trying to access your own drive account always i sugest you look into using a service account instead. Service accounts are pre approved you share a folder on your google drive account with it and it will have access to that drive account as long as you dont remove the permissions. I have a post about how service accounts work Google developer for beginners service account
Example:
// Load the Google API PHP Client Library.
require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
return getServiceAccountClient();
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
* Scopes will need to be changed depending upon the API's being accessed.
* array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
code ripped form my sample project serviceaccount.php
Usage:
require_once __DIR__ . '/vendor/autoload.php';
session_start();
require_once __DIR__ . '/ServiceAccount.php';
$client = getGoogleClient();
$service = new Google_Service_Drive($client);
All your requests then get sent using $service
I want to write a server php script which will access my google drive and copy a file there.
The server has to save credentials for my google drive and not ask for authorisation.
All the examples I saw describe web applications there various users can perform actions on their drives. For example here https://developers.google.com/drive/v3/web/quickstart/php
How can I save all the needed credentials on my server.
After a long research and reading google documentation and examples I found a way that works for me.
You need a service account. This account will access the google drive data.
I work with google domain, therefore I needed to grant domain wide authority to this service account.
Here you can find how to create a service account and grant it domain wide authority however this link does not has PHP examples
Here you can find the same instructions with PHP examples
Please pay attention that you need JSON type key file when you create a service account.
I used Google Application Default Credentials.
Finally this is working code snippet:
<?php
require_once '/path/to/google-api-php-client/vendor/autoload.php';
putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json');
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->setScopes(['https://www.googleapis.com/auth/drive']);
$client->setSubject('email_of_account#you_want_to_work.for');
$service = new Google_Service_Drive($client);
//Create a new folder
$fileMetadata = new Google_Service_Drive_DriveFile(
array('name' => 'Invoices',
'mimeType' => 'application/vnd.google-apps.folder'));
$file = $service->files->create($fileMetadata, array('fields' => 'id'));
echo $file->id;
?>
I recommend you look into using a service account. A service account is like a dummy user which is pre authenticated. This means that you can share a folder on your google drive account with the service account and the service account will be able to upload to it. I have an article on how Service accounts work. Google Developers console Service account
There are a few things you need to remember when working with service accounts though. Mainly permissions when the service account uploads a file by default it owns the file so you will need to grant your personal account permissions to the file after it is uploaded. Its just an extra step.
The PHP client library has a sample for using service account authentication but it doesn't have drive you will have to alter the books part to be google drive. Service-account.php
require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
return getServiceAccountClient();
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
* Scopes will need to be changed depending upon the API's being accessed.
* array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
This may also help my google drive samples
I need to get a list of files from a public marked folder on Google Drive.
Even though the folders are marked as public, the Drive API by design requires my code to send an authenticated request – fair enough.
However, I am not being authorized correctly. Instead I get an error: "Error refreshing the OAuth2 token, message: '{ "error": "unauthorized_client" }', and I cannot see why.
<?php
require_once 'google-api-php-client/src/Google/autoload.php';
require_once 'google-api-php-client/src/Google/Client.php';
require_once 'google-api-php-client/src/Google/Service/Drive.php';
require_once 'google-api-php-client/src/Google/Service/Oauth2.php';
// session_start();
define('DRIVE_SCOPE', 'https://www.googleapis.com/auth/drive');
define('SERVICE_ACCOUNT_EMAIL', 'blablablablabla-blablablabla51#developer.gserviceaccount.com');
define('SERVICE_ACCOUNT_PKCS12_FILE_PATH', 'Bla-blabla29db83.p12');
/**
* Build and returns a Drive service object authorized with the service accounts
* that acts on behalf of the given user.
*
* #param userEmail The email of the user.
* #return Google_Service_Drive service object.
*/
function buildService($userEmail) {
$key = file_get_contents(SERVICE_ACCOUNT_PKCS12_FILE_PATH);
$auth = new Google_Auth_AssertionCredentials(
SERVICE_ACCOUNT_EMAIL,
array(DRIVE_SCOPE),
$key);
$auth->sub = $userEmail;
$client = new Google_Client();
$client->setAssertionCredentials($auth);
// This fails:
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion();
}
return new Google_Service_Drive($client);
}
/**
* Print files belonging to a folder.
*
* #param Google_Service_Drive $service Drive API service instance.
* #param String $folderId ID of the folder to print files from.
*/
function printFilesInFolder($service, $folderId) {
// Omitted code
}
$service = buildService('blablabla#gmail.com');
printFilesInFolder($service, 'blablablablablablablabla');
?>
The service email account and the pkcs12 files are correctly entered.
I'm thinking of the $userEmail address. Is there somewhere where I should create a relation between the service and that address? Currently I'm using the mail address that owns the project and has set up the OAuth2 certificate being used.
This uses the newest Drive API. I have found other questions and explanations but they usually rely on the obsolete Documents API instead.
I am trying to run the code from the command line, not from a web page.