Apply original comments in a copied document - google-drive-api

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

Related

Get Labelled Files in GDrive

I wrote a code in Google Appscript to index all files in GDrive and try to identify if those files are labelled or not.
For summary, the Labels feature only available in GWorkspace business account and only the admin of the entreprise who have edit access of this feature.
I tried to use seacrhFiles(param) however the "Labels" isn't recognized by q string query Google, do we have another method to do it ?
Thank you,
Features that are part of workspace business accounts. Are not part of the public Google drive api.
You can tell by looking at the file resource object. It does not contain a field called label.
Your not going to find a method in the google drive api that will return a label on a file.
Even a Google Document resource object doesn't have it.
There may be a restricted api just for workspace users you would have to ask. But its not something i have heard of.
There will be a labels API that will allow you to to create, edit, apply, and remove labels programmatically
As of now, this API is still in development (beta stage), but you can get access to it if you contact drive-labels-feedback#google.com.
References:
https://support.google.com/a/answer/9292382?hl=en&fl=1#zippy=%2Cis-it-possible-to-apply-labels-through-an-api
https://issuetracker.google.com/167701931

How to Download G Suite docs/sheets to pdf/xls programatically?

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.

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.

Google Drive OAuth 2 flow giving invalid_scope error

My Google Drive app requests the following scopes when exchanging a code for an access token:
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/drive.install
In particular, this is the query string of the URL that is eventually being requested from Google during the exchange:
code=XXXXXXXXXX&grant_type=authorization_code&redirect_uri=XXXXXXXXXXX& scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file+ https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install &client_id=XXXXXX.apps.googleusercontent.com&client_secret=XXXXXX
The response is a 400 error, with the error message "invalid_scope". What am I doing wrong?
[Edit] Additional information:
The error only happens when the user clicks through from Google Drive to create a new document. If I initiate the authentication/authorization flow from my own app, the list of scopes is accepted just fine. If the user clicks through the actual Drive app to create a new document, I get invalid_scopes.
The invalid scope is drive.install. If I remove that from the list of requested scopes when the user shows up to create a new document, things start working again. Does that make any sense at all? If the user has the Drive app installed already via us requesting that scope, why would requesting that same scope when the user shows up from the Drive app cause a problem of any kind?
I experienced a similar problem. The solution was to pass an array of scopes to the google client:
google_client.authorization.scope=[
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/drive.appdata']
rather than a concatenated string of scopes
google_client.authorization.scope="https://www.googleapis.com/auth/calendar.readonly%2Bhttps://www.googleapis.com/auth/drive.appdata"
The GET request in the Rails log looked identical, but the result was very different!
There is a chance that You might be using tab instead of space between two auth like
https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/drive.appdata
putted on new line to show tabs
always use one white space between these two links to authorization.
this had happened with me.
The new google api (at the moment of this answer is posted) requires scope attribute to be one string and scopes separated with white space. So like this
var SCOPES = "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/gmail.send";
gapi.auth2.init({
client_id:CLIENT_ID,
scope: SCOPES
}).then (...)
You could try not escaping the + symbols. That worked for us.

value attribute for Permissions Resource not populated in responses

Looking at:
https://developers.google.com/drive/v2/reference/permissions#resource
the values attribute which contains the email address value for the ACL is not returned by the API. It's not clear why the value isn't returned, I assume it's a privacy issue but it means Drive SDK can't support document migration (from one Google account to another) use cases where the old Documents List API v3 can:
https://developers.google.com/google-apps/documents-list/#retrieving_the_acl_for_a_document_file_or_collection
for now I'm looking at adding both Drive API and Docs v3 API scopes for my project and just using the Docs API call to retrieve the ACLs but ideally I'd be able to use just Drive API calls. Am I missing anything? Could a special scope be added to Drive API that allows ACL email address retrieval or is there some other way to handle this?
Jay
Thanks for your question Jay and thanks your answer Ali Afshar!
Unfortunately I do not understand how Google believes the following scenario should work without the email address of the users:
In Documents List API v3 you could copy a file A to file B, retrieve the ACL-information of file A (including the users email adresses) and simply add them as ACL to file B.
With Drive API you can retrieve almost the same Permission information, but without the user email address, which is still required to re-share file B to the same users.
As a sidenote: If you use GAS DefaultService DocsList, you can still receive the editor/viewers with getEditors() or getViewers(). If you manually share a file you can see all email addresses as well.
So if you ask me, the privacy issue is a valuable argument, but it does simply not apply here.
Jan
Since this question was posted, Drive API has been updated to allow permissionId to be sent on permissions.insert() (the id attribute). This allows for migration of ACLs without ever needing to know the email addresses (just straight copy the permissionIds over to the new file).
Additionally:
the permissions.getIdForEmail() API call provides a quick way to get the ID for a given email address
when returning permissions for a file with permissions.get() or permissions.insert(), the domain attribute is included which should help determine if the ACL raises security concerns.
I believe these features cover most use cases where the actual ACL email address retrieval was needed.
You are absolutely correct, the email address is hidden for privacy. It is not right that a user should see the email addresses of all other users that have access to the file. But I'm not sure I quite get the problem. Are you migrating using service accounts, or are users individually authorizing the migration?
The value in the permissions feed is consistent for each user, and that value is available in the about feed for a user. I assume you know the email address of the users, so you can authorize for each of them With a service account, and you can migrate the data.
You should not need the Drive API scope and the Docs v3 API scope, they are pretty much the same scope.
Also resurrecting this old thread, I had the same issue while migrating documents.
A workaround:
- Create a temporary folder
- Insert a permission for the user
- retrieve the id from the permission
Not nice, but works for me.