How to Download G Suite docs/sheets to pdf/xls programatically? - google-drive-api

I'm trying to download a Google doc to PDF or Sheet to XLS given an ID programmatically from the CLI.
Steps I've tried so far:
Contact support, but can't see a (?) help icon
Google for 10 minutes... I think Google Drive API does this (not sure)
Enable the Google Drive API
Signed up for a GCP project
Navigated thought the UI to enable the API
Trying the GET API results in 400 Invalid field selection using the fields for the ID of the document
I'm a bit stuck now and I am not sure how to proceed. Any suggestions?

Warning: hopefully
informative wall of text ahead! I've also uploaded the full Jupyter Notebook for you to clone and run here since, as you've realized, putting this sort of stuff together can be challenging.
Since we're going to be exporting files via the google drive API, we need credentials for that scope as detailed in https://developers.google.com/drive/api/v3/reference/files/export#auth.
However, first we need to choose an authentication method as detailed in https://developers.google.com/identity/protocols/oauth2#scenarios.
Since you mentioned creating a GCP project, I assume you're interested in using a GCP service account
as detailed in https://developers.google.com/identity/protocols/oauth2#serviceaccount
You can create a service account at https://console.developers.google.com/apis/credentials
or as explained in https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount
Make sure to enable domain-wide-delegation for that service account while creating it and grant it https://www.googleapis.com/auth/drive scope under https://admin.google.com/ac/owl/domainwidedelegation since you otherwise won't be able to impersonate other users, including yourself, and download their files.
We then use the SERVICE_ACCOUNT_FILE we just downloaded and the SCOPES we defined to create a Credentials object.
However, you'll need to first install the Python bindings for the Google API as per https://developers.google.com/drive/api/v3/quickstart/python (pip3 install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib)
With that, the following should be enough to authenticate to the API:
from googleapiclient.discovery import build
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = 'credentials.json'
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE,
scopes=SCOPES)
# Remember, you must have created credentials.json with domain-wide delegation!
credentials = credentials.with_subject('user#example.com')
# We then build a drive_v3 service using the credentials we just created
service = build('drive', 'v3', credentials=credentials)
We can access the files resource as shown in https://developers.google.com/drive/api/v3/reference/files/get and request the metadata of a file to which user#example.com has access https://docs.google.com/document/d/fileId/edit. In your case fileId=141g8UkQfdMQSTfIn475gHj1ezZVV16f5ONDxpWrrvts.
files = service.files()
print(service.files().get(fileId='1U3eMevKxTwDxzvOBUsqa36zvwBzKPVYOFgy3k_9vxb8').execute())
{'kind': 'drive#file', 'id':
'1U3eMevKxTwDxzvOBUsqa36zvwBzKPVYOFgy3k_9vxb8', 'name': 'empty',
'mimeType': 'application/vnd.google-apps.document'}
We access the files resource again but this time to export the file as detailed in
https://developers.google.com/resources/api-libraries/documentation/drive/v3/python/latest/drive_v3.files.html#export
This could also be achieved using https://developers.google.com/drive/api/v3/manage-downloads.
Valid MIME types are listed in https://developers.google.com/drive/api/v3/ref-export-formats.
fconr = files.export(fileId='1U3eMevKxTwDxzvOBUsqa36zvwBzKPVYOFgy3k_9vxb8',
mimeType='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
fcont = fconr.execute()
print('{}...'.format(fcont[:10]))
file = open("/tmp/sample.doc", "wb")
file.write(fcont)
file.close()
b'MN\xc30\x10\x85O\xc0\x1d"'...
As you can see, fcont contains a binary blob that corresponds to the document and of which I'm showing the first 10 bytes. Finally, the blob is saved to sample.doc.
ls -alh1 /tmp/sample.doc
-rw-rw-r-- 1 jdsalaro jdsalaro 6,0K Jan 20 23:38 /tmp/sample.doc
As mentioned above, I encourage you to experiment with the Jupyter notebook once you've created the service account with domain-wide delegation, have saved it to credentials.json and have granted it the https://www.googleapis.com/auth/drive scope.

Related

upload file via Data Management API, but return 403

The scope includes data:write data:create, and accessToken can be obtained. But when uploading files through Api, it returns 403 and the message prompt: "No write access".
I checked it according to the document, which means: "The Authorization was successfully validated but permission is not granted. Don't try again unless you solve permissions first.".
I'm not sure if there is something wrong with the permission when get the accessToken, or because the free user has no permission to call the upload file interface. But it worked normally before.
you are correct the scope can be data:write or data:create with uploading file. The most possibility is you are trying to uploading to a bucket which is NOT created by this Forge app. e.g. you may have a few Forge apps (different client id and secret). The bucket may be created by the other Forge app, so this app cannot write the data. Or even the bucket is created by other customers, while you thought you are the owner. Please GET:Buckets firstly to check which buckets available with this app (client id + secret) :forge.autodesk.com/en/docs/data/v2/reference/http/buckets-GET

Connecting Google add-on to google vm instance

I am developing an add-on for google drive.
As a part of the functionality of this add-on, I would like to incorporate a google vm server that performs some processing on a user's google drive files (i.e. you click on a file through the add-on, send a request with a download link to the server, the server downloads the file, then finally responds to the request with some helpful information about the file). Both the apps script for the add-on and the vm instance are connected to the same google "project."
I am struggling with google's OAuth2.0 system and how I can connect the authorization of the add-on and the vm instance together.
Currently, when users open the add-on for the first time, they are brought to the authorization screen like so .
Once they authorize, my add on has access to all the expected scopes, including read access to google drive files.
Now I want my server to have access to them as well. Unfortunately, I do not understand how to do this.
I have tried simply requesting the url returned from file.getDownloadUrl() in python. While the request returns a status code of 200, I cannot seem to get the file to download.
I have also looked into the Google Drive API for python (I am running a flask server). Unfortunately, it appears that I need an entirely new authorization flow to make it work.
Any clarity on this issue would be greatly appreciated. Frankly, I find google's documentation on this matter very scattered and confusing. So, even knowing the right place to look would extremely helpful.
Much Thanks!
EDIT
I am adding some additional code to help provide some clarity. This is currently how I make a request to my server from the add-on:
var route = http://exampleurl.com/process
var data = {
'oAuthToken': ScriptApp.getOAuthToken(),
'stateToken': ScriptApp.newStateToken().withTimeout(120).createToken(),
'fileId': e.parameters.fileId,
'fileType': e.parameters.fileMimeType
};
var options = {
'method' : 'post',
'payload' : data
};
var response = UrlFetchApp.fetch(route, options);
This code successfully sends information to my vm instance running my server.
Now, I need to authorize the server to download the file specified by fileId.
When developing, I closely followed this tutorial to set up OAuth2.0 access to the Drive API. Here are two key routes:
#app.route('/google/login')
#no_cache
def login():
session = OAuth2Session(CLIENT_ID, CLIENT_SECRET,
scope=AUTHORIZATION_SCOPE,
redirect_uri=AUTH_REDIRECT_URI)
uri, state = session.create_authorization_url(AUTHORIZATION_URL)
flask.session[AUTH_STATE_KEY] = state
flask.session.permanent = True
return flask.redirect(uri, code=302)
#app.route('/google/auth')
#no_cache
def google_auth_redirect():
req_state = flask.request.args.get('state', default=None, type=None)
if req_state != flask.session[AUTH_STATE_KEY]:
response = flask.make_response('Invalid state parameter', 401)
return response
session = OAuth2Session(CLIENT_ID, CLIENT_SECRET,
scope=AUTHORIZATION_SCOPE,
state=flask.session[AUTH_STATE_KEY],
redirect_uri=AUTH_REDIRECT_URI)
oauth2_tokens = session.fetch_access_token(
ACCESS_TOKEN_URI,
authorization_response=flask.request.url)
flask.session[AUTH_TOKEN_KEY] = oauth2_tokens
return flask.redirect(BASE_URI, code=302)
Is there a way to plug in the two tokens I generate from the add-on into this Oauth flow? It appears that Google isn't anticipating this setup given that I am required to provide a redirect URL, which wouldn't make much sense in the case of my add-on/server tech stack.
Currently, you can successfully send the access token from the Apps Script project (retrieved with getOAuthToken()), to the flask server.
Since you already got the access token, you don't need to go through all the OAuth process as defined here (use the application credentials to request the access token, provide user consent, redirect, etc.). Sending the access token through the API request is actually the last step in the OAuth flow.
You just need to use the token to build the service, and the server will be able to access the file.
Using access token to build service:
from googleapiclient.discovery import build
import google.oauth2.credentials
ACCESS_TOKEN = requestBody['oAuthToken'] # Data coming from Apps Script via UrlFetch
creds = google.oauth2.credentials.Credentials(ACCESS_TOKEN)
drive_service = build('drive', 'v3', credentials=creds) # Build Drive service
Using Drive service to download files:
After you've build the Drive service to access the API, you can use this code sample to download Google Documents, or this one for other files (both examples include a tab with a Python sample).
Note:
You might need to Install the Google Client Library if you haven't done so.
Reference:
Using OAuth 2.0 to Access Google APIs

Apply original comments in a copied document

I am trying to make a copy of a document with comments by using the Google drive api. The copy itself succeeds, but the copied document is created without any comments. I tried to loop over all comments in the original document and apply those on the copied document to overcome this, and this does apply all content of all comments, but with me as the author for the comments. I want the original author to also be the author of the comment in the copy.
I understand that this behavior probably is wanted, else I could add comments that seem to come from someone else, but if I open any document with comments in google drive, there is a File -> Make a copy option that has a checkbox to copy all comments:
If I do that, a new copy is created, and comments are added from the original author (with a note that says the comment is copied from original document). Is there any way to do this from the API?
First of all, answering your doubt about the comment you made. Yes, all languages wrap the REST-calls to the API.
So, I was checking the Drive API using the Try this API and the comments can't be copied as you would want.
Therefore, as a workaround(That's why I asked you about the language you are using, to be able to make an example code), you could use a service account to impersonate any user you want and in that way, the comment will be registered as if he/she did it.
I will list you a series of links that will help you to set up a service account, before being able to use it.
1) Enable APIs you want(in this case only the Drive API)
2) Create the service account and credentials.
3) Delegate domain-wide authority to your service.
4) Then you can use the following code to generate a new comment as the user you are impersonating:
from googleapiclient import discovery, errors
from httplib2 import Http
from oauth2client import file, client, tools
from google.oauth2 import service_account
SERVICE_ACCOUNT_FILE = 'service_account.json'
SCOPES = ['https://www.googleapis.com/auth/drive']
# The user we want to "impersonate"
USER_EMAIL = "impersonated-user#your-domain.com"
# Set the credentials using the .json and the SCOPES
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
delegated_credentials = credentials.with_subject(USER_EMAIL)
try:
# Insert the comment
service = discovery.build('drive', 'v3', credentials=delegated_credentials)
service.comments().create(fileId="your file id", fields="*", body={
"content": "TESTTT"
}).execute()
except errors.HttpError as err:
print('\n---------------You have the following error-------------')
print(err)
print('---------------You have the following error-------------\n')
Notice
Service Accounts are only available if you have a G Suite account and you have adminĀ“s access.
Docs
For building the code I passed you, I got help from these topics:
google.oauth2.service_account
Drive API comments
googleapiclient

How can I publish a Google App Script using a domain-wide-delegation service account?

I'm trying to use the what Google terms a 'Domain-wide delegation' service account: https://developers.google.com/admin-sdk/directory/v1/guides/delegation
The specific API I'm trying to access with this delegation is: https://developers.google.com/apps-script/api/
Here's the code:
from google.oauth2 import service_account
import googleapiclient.discovery
import json
import os
SCOPES = ['https://www.googleapis.com/auth/script.projects', 'https://www.googleapis.com/auth/drive']
SERVICE_KEY = json.loads(os.environ['SERVICE_KEY'])
credentials = service_account.Credentials.from_service_account_info(SERVICE_KEY, scopes=SCOPES)
delegated_credentials = credentials.with_subject('fred.bloggs#my-gapps-domain.com')
script = googleapiclient.discovery.build('script', 'v1', credentials=delegated_credentials)
response = script.projects().get(scriptId='<myscriptId>').execute()
print json.dumps(response)
This fails with:
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method.', u'{\n "error" : "unauthorized_client",\n "error_description" : "Client is unauthorized to retrieve access tokens using this method."\n}')
I'm pretty sure I've followed all the steps at https://developers.google.com/api-client-library/python/auth/service-accounts, including authorizing the 'https://www.googleapis.com/auth/script.projects' scope with the client ID of the service account json key I downloaded.
Note, I was able to successfully get this particular snippet to work by skipping with_subject, and going in to the Script dashboard as the user, and 'sharing' the script project.
Unfortunately though that still doesn't allow upload a new set of files (as 'sharing' doesn't give the ability to delete). It does at least confirm my calling code is correct, albeit not authenticating properly with the json service key.
To clarify:
The script in question is what I believe is termed 'standalone' (not a web app)
It's owned by a bot user I've setup just like a regular GSuite user (as I didn't want scripts in regular user's Google Drives)
The script started in a Google Cloud Project that seemed to be automatically created, and with 'No organisation'. I then created a new project manually within the organisation, and moved the script to that project.
There's an official Google Apps Script client now, so I asked there too https://github.com/google/clasp/issues/225#issuecomment-400174500 - although they use the Javascript API (via Typescript), the principles should be the same.

GoogleDrive REST API Authentication without intervention

What i need to do is to authenticate myself into the GoolgeDrive API (I use it in python) to access my drive to do some automatique task.
I would like that my script run alone every hour without any intervention. So what i need is a way to create a credentials object with my login and password.
Is it possible without any redirection and so on?
I think it is not possible to achieve this with user id and password. The way Google Drive implements this is using a refresh token. That means that you authenticate your app one time interactively. This enables you app for one hour. If the refresh token mechanism is properly configured, the app will renew the token every time it needs it subsequently.
Basically the following steps need to be taken
Visit console.developers.google.com
Register a project and obtain Oauth Client IDs
Select as type web server and enter "http://localhost:8080/" as authorized redirect URL
Download the client_secrets.json and store it in the root of your python app
Create a file called "settings.yaml" at the same place with the following content
client_config_backend: settings
client_config:
client_id: YOUR_CLIENT_ID_GOES_HERE
client_secret: YOUR_CLIENT_SECRET_GOES_HERE
save_credentials: True
save_credentials_backend: file
save_credentials_file: credentials.son
get_refresh_token: True
oauth_scope:
- https://www.googleapis.com/auth/drive.file
- https://www.googleapis.com/auth/drive.install
In your python code you need to do proper authentication and credential saving:
gauth = GoogleAuth()
gauth.settings["get_refresh_token"]=True
gauth.settings["approval_prompt"]="force"
if exists(self.credsFile):
gauth.LoadCredentialsFile(self.credsFile)
if gauth.credentials is None:
# Authenticate if they're not there
gauth.LocalWebserverAuth()
gauth.SaveCredentialsFile(self.credsFile)
elif gauth.access_token_expired:
# Refresh them if expired
gauth.Refresh()
gauth.Authorize()
else:
# Initialize the saved creds
gauth.Authorize()
# Save the current credentials to a file
gauth.SaveCredentialsFile(self.credsFile)
self.driveInstance= GoogleDrive(gauth)
Make sure that you pass in self.credsFile as a valid filename
Executing this code should give you a URL at the console. Copy it to a browser, authenticate and give your consent. Google should ask you for two consents, the second is to authenticate for Google Drive - which is actually done by the refresh token.
The redirect url from the initial credential config in the developer console is called once the consent is given. It calls the temporary web server started by your application. This is how the call back is done. (This implies you have to run browser and your app on the same machine for this step - you may copy over all three files to your server)
From now on your app should run forever without requiring user interaction.