Getting Google file picker to work with drive.file scope - ruby-on-rails

I'm trying to use the Google file picker to select a document and then update its permissions. I don't need access to all Drive files, just those selected.
However, when using the https://www.googleapis.com/auth/drive.file scope, I'm getting a 404 error when I try to change the permissions of the doc. I don't get this error if I use the https://www.googleapis.com/auth/drive scope, but this gives me more access than I need.
Is there any way to get the file picker working with the more limited https://www.googleapis.com/auth/drive.file scope?
Here's my file picker code:
<!-- The standard Google Loader script. -->
<script src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
// Use the Google Loader script to load the google.picker script.
//google.setOnLoadCallback(createPicker);
google.load('picker', '1');
// Create and render a Picker object
function createPicker() {
var view = new google.picker.DocsView(google.picker.ViewId.DOCS);
view.setMode(google.picker.DocsViewMode.LIST);
//view.setMimeTypes("image/png,image/jpeg,image/jpg");
var picker = new google.picker.PickerBuilder()
//.enableFeature(google.picker.Feature.NAV_HIDDEN)
//.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId("<%= ENV['GOOGLE_ID']%>")
.setOAuthToken("<%= current_user.token %>") //Optional: The auth token used in the current Drive API session.
.addView(view)
.addView(new google.picker.DocsUploadView())
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
// callback implementation.
function pickerCallback(data) {
var url = 'nothing';
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
url = doc[google.picker.Document.URL];
title = doc.name;
id = doc.id;
type = doc.type;
embed = doc[google.picker.Document.EMBEDDABLE_URL ];
}

Answering my own question thanks to help from folks on the Google Drive Developer discussion on Google Plus:
This does work. I was using the wrong App_ID in my picker implementation - I needed to use only the numeric string at the beginning of the client ID. The other problem, it doesn't work on localhost, only in production.
Full discussion here: https://plus.google.com/u/0/108228914813783364664/posts/RgvmZwJcbE8

If you're modifying a file that's not created by your application, there is no more restricted scope other than the drive scope.

Related

Link to a specific sheet in published Google Sheet

I see similar questionns has been asked multiple times before, but I cant seem to get them to work. I've also read that Google changed how their URLs are built up, so most of the solutions were deprecated unfortunately.
I'm looking for a link to a specific sheet of a workbook that has been published. I've made a simple workbook to test, and the published link looks like this:
https://docs.google.com/spreadsheets/d/e/2PACX-1vRrmEbjecLvXhbm409pa6JJXZd_ZXTG8Zt6OevIUs5Axq5oxlCZKU0QXk-2lW05HyXJ2B4Bzy3bG-4L/pubhtml
As you can see there is a top menu to change between the sheets, but that doesn't affect the URL.
Is there any way I can get a URL to "Sheet2" directly? Or is that dependant on having the Sheet ID (I'm not the owner of said spreadsheet)?
I believe your goal as follows.
You want to retrieve the values from Sheet2 from the URL of https://docs.google.com/spreadsheets/d/e/2PACX-1vRrmEbjecLvXhbm409pa6JJXZd_ZXTG8Zt6OevIUs5Axq5oxlCZKU0QXk-2lW05HyXJ2B4Bzy3bG-4L/pubhtml.
The owner of this Spreadsheet is not you.
You don't know the Spreadsheet ID and each sheet ID in the Spreadsheet. You know only the URL of https://docs.google.com/spreadsheets/d/e/2PACX-###/pubhtml.
Under above situation, you want to retrieve the direct URL of the sheet 2.
For above goal, how about this answer?
Issue and workarounds:
Unfortunately, in the current stage, it seems that the Spreadsheet ID and each sheet ID cannot be directly retrieved from the URL of https://docs.google.com/spreadsheets/d/e/2PACX-###/pubhtml. I think that this is the current specification. Also I think that this reason might be due to the security. So in order to achieve your goal, it is required to think of the workaround.
In this answer, as a workaround, I would like to achieve your goal using Web Apps created by Google Apps Script. When Web Apps is used, the directlink of Sheet2 can be retrieved.
Flow:
The flow of this workaround is as follows.
Download the Google Spreadsheet as a XLSX data from the URL of https://docs.google.com/spreadsheets/d/e/2PACX-###/pubhtml.
Convert the XLSX data to Google Spreadsheet.
Publish the converted Google Spreadsheet to Web.
Retrieve the URLs of each sheet.
Usage:
Please do the following flow.
1. Create new project of Google Apps Script.
Sample script of Web Apps is a Google Apps Script. So please create a project of Google Apps Script.
If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.
2. Prepare script.
Please copy and paste the following script (Google Apps Script) to the script editor. And please enable Google Drive API at Advanced Google services. This script is for the Web Apps.
function doGet(e) {
const prop = PropertiesService.getScriptProperties();
const ssId = prop.getProperty("ssId");
if (ssId) {
DriveApp.getFileById(ssId).setTrashed(true);
prop.deleteProperty("ssId");
}
const inputUrl = e.parameter.url;
const re = new RegExp("(https?:\\/\\/docs\\.google\\.com\\/spreadsheets\\/d\\/e\\/2PACX-.+?\\/)");
if (!re.test(inputUrl)) return ContentService.createTextOutput("Wrong URL.");
const url = `${inputUrl.match(re)[1]}pub?output=xlsx`;
const blob = UrlFetchApp.fetch(url).getBlob();
const id = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS, title: "temp"}, blob).id;
prop.setProperty("ssId", id);
Drive.Revisions.update({published: true, publishedOutsideDomain: true, publishAuto: true}, id, 1);
const sheets = SpreadsheetApp.openById(id).getSheets();
const pubUrls = sheets.map(s => ({[s.getSheetName()]: `https://docs.google.com/spreadsheets/d/${id}/pubhtml?gid=${s.getSheetId()}`}));
return ContentService.createTextOutput(JSON.stringify(pubUrls)).setMimeType(ContentService.MimeType.JSON);
}
In this case, the GET method is used.
In this script, when the below curl command is run, the Google Spreadsheet is downloaded as a XLSX data, and the XLSX data is converted to Google Spreadsheet. Then, the converted Spreadsheet is published to the web. By this, the direct links of each sheet can be retrieved.
Also, in this script, it supposes that the original Spreadsheet is changed. So if you run the curl command again, the existing Spreadsheet is deleted and new Spreadsheet is created by downloading from the original Spreadsheet. In this case, the URLs are updated.
So if the Spreadsheet is not changed, you can continue to use the retrieved URLs. Of course, you can also directly use the downloaded and converted Spreadsheet.
3. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Select "Anyone, even anonymous" for "Who has access to the app:".
In this case, no access token is required to be request. I think that I recommend this setting for your goal.
Of course, you can also use the access token. At that time, please set this to "Anyone".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
4. Run the function using Web Apps.
This is a sample curl command for requesting Web Apps. Please set your Web Apps URL.
curl -L "https://script.google.com/macros/s/###/exec?url=https://docs.google.com/spreadsheets/d/e/2PACX-1vRrmEbjecLvXhbm409pa6JJXZd_ZXTG8Zt6OevIUs5Axq5oxlCZKU0QXk-2lW05HyXJ2B4Bzy3bG-4L/pubhtml"
In this case, the GET method is used at Web Apps side. So you can also directly access to the above URL using your browser.
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 the Web Apps. Please be careful this.
In this answer, I thought that you might use this from outside. So I used Web Apps. If you want to directly retrieved from the Google Apps Script, you can also use the following script.
function myFunction() {
const inputUrl = "https://docs.google.com/spreadsheets/d/e/2PACX-1vRrmEbjecLvXhbm409pa6JJXZd_ZXTG8Zt6OevIUs5Axq5oxlCZKU0QXk-2lW05HyXJ2B4Bzy3bG-4L/pubhtml";
const prop = PropertiesService.getScriptProperties();
const ssId = prop.getProperty("ssId");
if (ssId) {
DriveApp.getFileById(ssId).setTrashed(true);
prop.deleteProperty("ssId");
}
const re = new RegExp("(https?:\\/\\/docs\\.google\\.com\\/spreadsheets\\/d\\/e\\/2PACX-.+?\\/)");
if (!re.test(inputUrl)) throw new Error("Wrong URL.");
const url = `${inputUrl.match(re)[1]}pub?output=xlsx`;
const blob = UrlFetchApp.fetch(url).getBlob();
const id = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS, title: "temp"}, blob).id;
prop.setProperty("ssId", id);
Drive.Revisions.update({published: true, publishedOutsideDomain: true, publishAuto: true}, id, 1);
const sheets = SpreadsheetApp.openById(id).getSheets();
const pubUrls = sheets.map(s => ({[s.getSheetName()]: `https://docs.google.com/spreadsheets/d/${id}/pubhtml?gid=${s.getSheetId()}`}));
console.log(pubUrls); // You can see the URLs for each sheet at the log.
}
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Advanced Google services
publish a Google Spreadsheet through Google Apps Scripts
Added:
As another workaround, when the original Spreadsheet is often changed, and the number of sheet is constant in the original Spreadsheet, and then, you want to retrieve only values, you can also use the following script. In this script, the URL is not changed even when the script is run again. So you can continue to use the URL.
Sample script:
function myFunction() {
const inputUrl = "https://docs.google.com/spreadsheets/d/e/2PACX-1vRrmEbjecLvXhbm409pa6JJXZd_ZXTG8Zt6OevIUs5Axq5oxlCZKU0QXk-2lW05HyXJ2B4Bzy3bG-4L/pubhtml";
const re = new RegExp("(https?:\\/\\/docs\\.google\\.com\\/spreadsheets\\/d\\/e\\/2PACX-.+?\\/)");
if (!re.test(inputUrl)) throw new Error("Wrong URL.");
const url = `${inputUrl.match(re)[1]}pub?output=xlsx`;
const blob = UrlFetchApp.fetch(url).getBlob();
const prop = PropertiesService.getScriptProperties();
let sheets;
let ssId = prop.getProperty("ssId");
if (ssId) {
const temp = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS, title: "tempSpreadsheet"}, blob).id;
const tempSheets = SpreadsheetApp.openById(temp).getSheets();
sheets = SpreadsheetApp.openById(ssId).getSheets();
tempSheets.forEach((e, i) => {
const values = e.getDataRange().getValues();
sheets[i].getRange(1, 1, values.length, values[0].length).setValues(values);
});
DriveApp.getFileById(temp).setTrashed(true);
} else {
ssId = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS, title: "copiedSpreadsheet"}, blob).id;
Drive.Revisions.update({published: true, publishedOutsideDomain: true, publishAuto: true}, ssId, 1);
prop.setProperty("ssId", ssId);
sheets = SpreadsheetApp.openById(ssId).getSheets();
}
const pubUrls = sheets.map(s => ({[s.getSheetName()]: `https://docs.google.com/spreadsheets/d/${ssId}/pubhtml?gid=${s.getSheetId()}`}));
console.log(pubUrls); // You can see the URLs for each sheet at the log.
}

Use Google Sheet Authentication for application, not for end user

I am trying to use Google Sheet in my application. This google sheet is not related to end user but will be under my own account. As per some tutorial on Google Sheets API I have created project on developer console but during OAuth 2.0 authentication, Google display login dialog to end user. As said before, this sheet is not related to end user so how can I fix my own Google credentials in code so that it just work with sheet, without asking user for his credentials?
Here is my code snippet
var clientSecret = new Google.Apis.Auth.OAuth2.ClientSecrets() { ClientId = "*******.apps.googleusercontent.com", ClientSecret = "*******" };
var credential = Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.AuthorizeAsync(clientSecret,
Scopes,
"myemail#google.com",
System.Threading.CancellationToken.None).Result;
var service = new Google.Apis.Sheets.v4.SheetsService(new Google.Apis.Services.BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
Are you looking to publish the article publicly?
https://support.google.com/docs/answer/37579?hl=en
You can use this method, then embed the sheet in an iframe on your site
It looks there are no such ways yet. So finally I decided to use local database and then export content to Excel using ClosedXML library.

Unable to save a query as a view table

I have a query that runs and can see the results. But while trying to save the query as a view table, I get error message saying
Failed to save view. No suitable credentials found to access Google
Drive. Contact the table owner for assistance.
I think the problem is caused by a table used in the query. The table is uploaded from a google sheet (with source URI), own by me. I have tried to enable Google Drive API from the project but no luck. Not sure how I can give BigQuery access to Google Drive.
I suspect the problem you are hitting is one of OAuth Scopes. In order to talk to the Google Drive API to read data, you need to use credentials that were granted access to that API.
If you are using the BigQuery web UI and have not explicitly granted access to Drive, it won't work. For example, the first time I tried to "Save to Google Sheets", the BigQuery UI popped up an OAuth prompt asking me to grant access to my Google Drive. After this it could save the results. Try doing this to make sure your credentials have the Drive scope and then "Save View" again.
If you are using your own code to do this, you should request scope 'https://www.googleapis.com/auth/drive' in addition to the 'https://www.googleapis.com/auth/bigquery' scope you are already using to talk to BigQuery.
If you are using the bq client, it has been updated to request this scope, but you may need to re-initialize your authentication credentials. You can do this with bq init --delete_credentials to remove the credentials, then your next action we re-request credentials.
Using Google App Script this worked for me:
function saveQueryToTable() {
var projectId = '...yourprojectid goes here...';
var datasetId = '...yourdatesetid goes here...';
var sourceTable = '...your table or view goes here...';
var destTable = '...destination table goes here...';
var myQuery;
//just a random call to activate the Drive API scope
var test = Drive.Properties.list('...drive file id goes here...')
//list all tables for the particular dataset
var tableList = BigQuery.Tables.list(projectId, datasetId).getTables();
//if the table exist, delete it
for (var i = 0; i < tableList.length; i++) {
if (tableList[i].tableReference.tableId == destTable) {
BigQuery.Tables.remove(projectId, datasetId, destTable);
Logger.log("DELETED: " + destTable);
}
};
myQuery = 'SELECT * FROM [PROJECTID:DATASETID.TABLEID];'
.replace('PROJECTID',projectId)
.replace('DATASETID',datasetId)
.replace('TABLEID',sourceTable)
var job = {
configuration: {
query: {
query: myQuery,
destinationTable: {
projectId: projectId,
datasetId: datasetId,
tableId: destTable
}
}
}
};
var queryResults = BigQuery.Jobs.insert(job, projectId);
Logger.log(queryResults.status);
}
The 'trick' was a random call to the Drive API to ensure both the BigQuery and Drive scopes are included.
Google Apps Script Project Properties

YouTube API Academy

I just completed the YouTube API tutorials on Codecademy and successfully managed to display results relating to a given 'q' value in the console window provided using the following code:
// Helper function to display JavaScript value on HTML page.
function showResponse(response) {
var responseString = JSON.stringify(response, '', 2);
document.getElementById('response').innerHTML += responseString;
}
// Called automatically when JavaScript client library is loaded.
function onClientLoad() {
gapi.client.load('youtube', 'v3', onYouTubeApiLoad);
}
// Called automatically when YouTube API interface is loaded (see line 9).
function onYouTubeApiLoad() {
// This API key is intended for use only in this lesson.
// See http://goo.gl/PdPA1 to get a key for your own applications.
gapi.client.setApiKey('AIzaSyCR5In4DZaTP6IEZQ0r1JceuvluJRzQNLE');
search();
}
function search() {
// Use the JavaScript client library to create a search.list() API call.
var request = gapi.client.youtube.search.list({
part: 'snippet',
q: "Hello",
});
// Send the request to the API server,
// and invoke onSearchRepsonse() with the response.
request.execute(onSearchResponse);
}
// Called automatically with the response of the YouTube API request.
function onSearchResponse(response) {
showResponse(response);
}
and:
<!DOCTYPE html>
<html>
<head>
<script src="search.js" type="text/javascript"></script>
<script src="https://apis.google.com/js/client.js?onload=onClientLoad" type="text/javascript"></script>
</head>
<body>
<pre id="response"></pre>
</body>
</html>
The problem I am having now is that I have taken this code and put it into my own local files with the intention of furthering my understanding and manipulating it work in a way which suits me, however it just returns a blank page. I assume that it works on Codecademy because they use a particular environment and the code used perhaps only works within that environment, I am surprised they wouldn't provide information on what changes would be required to use this outside of their given environment and was hoping someone could shed some light on this? Perhaps I am altogether wrong, if so, any insight would be appreciated.
Browser Console Output:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('file://') does not match the recipient window's origin ('null').
I also had the same problem but it was resolved when I used Xampp. What you have to do is install xampp on your machine and then locate its directory. After You will find a folder named "htdocs". Just move your folder containing both js and HTML file into this folder. Now you have to open Xampp Control Panel and click on start button for both - Apache and SQL server. Now open your browser and type in the URL:
http://localhost/"(Your htdocs directory name containing both of your pages)"
After this, click on .html file and you are done.

Get user followers in meteorjs

I'm trying to get the followers for a user that has authenticated through my meteorjs app. I have used the {{loginButtons}} template and have found where the users tokens are. However I now have to create my authorized request by hand and I was hoping this'd be easy. But it's really hard and I feel like I'm wasting time with trying to figure out a way to create the oauth_signature..
Any help is welcome!
Supposing it is Twitter you're talking about I might be able to help you out.
I just managed to do the same thing as you want to do.
This nice piece of code provides a client to the Twitter API: https://github.com/mynetx/codebird-js
Personally I have placed it in the server-folder in my app to avoid exposure of keys etc.
As the codebird-js code take use of XMLHttpRequests and node.js do not come with such functionality by default - at least in a meteor.js context - you have to add the XHR-functionality yourself.
This NPM did it for me: https://npmjs.org/package/xmlhttprequest
However, as you can not deploy your meteor app with additional npm packages I found this solution How can I deploy node modules in a Meteor app on meteor.com? that suggests placing it in the public folder.
Finally I added those lines of code in the codebird-js just below the line that says
var Codebird = function () {
var require = __meteor_bootstrap__.require;
var path = require('path');
var fs = require('fs');
var base = path.resolve('.');
var isBundle = fs.existsSync(base + '/bundle');
var modulePath = base + (isBundle ? '/bundle/static' : '/public') + '/node_modules';
var XMLHttpRequest = require(modulePath + '/xmlhttprequest').XMLHttpRequest;
Finally you have to provide your tokens generated at dev.twitter.com and find your user's tokens stored in the Users collection.
EDIT:
Whenenver you have the above you make a new Codebird object: var bird = new Codebird();
Then you set tokens:
bird.setToken(USER_ACCESS_TOKEN, USER_ACCESS_TOKEN_SECRET);
And makes the call:
bird.__call('friends/ids', {
screen_name': SCREEN_NAME,
user_id: TWITTER_ID
},
function(reply){
console.log(reply);
});
Note that USER_ACCESS_TOKEN, USER_ACCESS_TOKEN_SECRET, USER_NAME & TWITTER_ID in the above example are placeholders. They are all found in the Meteor Users collection.

Resources