Fetching files from Google Drive, from command-line in Node.js? - node.js

To facilitate updating of content on our information screen, we are looking at putting the content on Google Drive and then allowing the content to be synced via a NodeJS based application.
At the moment I am trying to test this approach using folder shared from my own account.
What I have so far, based on the documentation at https://github.com/google/google-api-nodejs-client/tree/master :
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var readline = require('readline');
const CLIENT_ID = 'xxxxxxxx.apps.googleusercontent.com';
const CLIENT_SECRET = '7h3c13n7s3cr37';
const REDIRECT_URL = 'https://accounts.google.com/o/oauth2/auth';
var oauth2Client = new OAuth2(
CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
var scopes = [
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/drive.metadata.readonly'
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: scopes // If you only need one scope you can pass it as string
});
console.log('past following URL into a web browser');
console.log(url);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Provide Key generated on web page ', (answer) => {
// store response key in file?
var drive = google.drive({ version: 'v2', auth: oauth2Client });
var folderId = 'mif01d3r';
drive.children.list({
auth: answer,
folderId: folderId,
}, function(error, response) {
if (error) {
console.log('err: ', error);
return;
}
console.log(response);
});
});
The current issue here is that the value for 'REDIRECT_URL' does not seem suitable. What should I be putting here for a command line application?

There is a much easier way. Manually generate a refresh token and then reference that (securely of course) from your app. In this way there is no auth required, no redirect URL's, etc etc.
See How do I authorise an app (web or installed) without user intervention? (canonical ?)

https://developers.google.com/api-client-library/python/auth/installed-app#choosingredirecturi is more for Python, but the below is applicable to all "Installed Applications"
urn:ietf:wg:oauth:2.0:oob
This value signals to the Google Authorization Server that the authorization code should be returned in the title bar of the browser, with the page text prompting the user to copy the code and paste it in the application. This is useful when the client (such as a Windows application) cannot listen on an HTTP port without significant client configuration.
See also:
https://developers.google.com/identity/protocols/OAuth2InstalledApp#choosingredirecturi
so it should be:
const REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob';

Related

YouTube Data v3 API - How to request all videos from a channel?

I am writing a Node.js script which will run in Lambda to periodically request the list of every video (public, unlisted, or private) for one of my organization's channels via the YouTube Data v3 API. In order to do this, it appears there are two steps:
Executing the channels/list call https://developers.google.com/youtube/v3/docs/channels/list to get the "Uploads" playlist.
const channelResult = await google.youtube('v3').channels.list({
auth: youtubeApiKey,
part: ['snippet', 'contentDetails'],
id: ["my_channel_id"]
});
Executing the playlistItems/list https://developers.google.com/youtube/v3/docs/playlistItems/list to see all the videos.
const videosResult = await google.youtube('v3').playlistItems.list({
auth: youtubeApiKey,
part: ['snippet', 'status'],
playlistId: "my_uploads_playlsit_id"
});
This only ever executes as a script running the cloud; there is no user interface or browser component. This all appeared to work in my test channel when I set the lone video there to public. But if I set it to private, I get:
The playlist identified with the request's <code>playlistId</code> parameter cannot be found.
What do I have to do in order to still access the Uploads playlist of my channel, and show private videos? Thanks!
With help from #stvar in the original question's comments, I was able to achieve this. The flow is as such:
Enable the YouTube Data API v3 from the Google Developers Console via the Enable APIs and Services.
Create a new OAuth client ID under YouTube Data API v3's "Credentials" pane and select Desktop app.
Save the client_id and client_secret. Make these accessible to your Node app via whatever environment variable method you prefer.
Create a separate script specifically for getting a refresh_token via YouTube Data API v3 OAuth
import { google } from 'googleapis';
import prompts from 'prompts';
console.log("about to execute oauth");
const yt_client_id = process.env.YOUTUBE_CLIENT_ID;
const yt_client_secret = process.env.YOUTUBE_CLIENT_SECRET;
const oauthClient = new google.auth.OAuth2({
clientId: yt_client_id,
clientSecret: yt_client_secret,
redirectUri: 'http://localhost'
});
const authUrl = oauthClient.generateAuthUrl({
access_type: 'offline', //gives you the refresh_token
scope: 'https://www.googleapis.com/auth/youtube.readonly'
});
const codeUrl = await prompts({
type: 'text',
name: 'codeURl',
message: `Please go to \n\n${authUrl}\n\nand paste in resulting localhost uri`
});
const decodedUrl = decodeURIComponent(codeUrl.codeURl);
const code = decodedUrl.split('?code=')[1].split("&scope=")[0];
const token = (await oauthClient.getToken(code)).tokens;
const yt_refresh_token = token.refresh_token;
console.log(`Please save this value into the YOUTUBE_REFRESH_TOKEN env variable for future runs: ${yt_refresh_token}`);
await prompts({
type: 'text',
name: 'blank',
message: 'Hit enter to exit:'
});
process.exit(0);
Save the refresh token in another environment variable, accessible to your main data-fetching script. Use it as such:
import { google } from 'googleapis';
console.log("Beginning youtubeIndexer. Checking for valid oauth.");
const yt_refresh_token = process.env.YOUTUBE_REFRESH_TOKEN;
const yt_client_id = process.env.YOUTUBE_CLIENT_ID;
const yt_client_secret = process.env.YOUTUBE_CLIENT_SECRET;
const yt_channel_id = process.env.YOUTUBE_CHANNEL_ID;
const oauthClient = new google.auth.OAuth2({
clientId: yt_client_id,
clientSecret: yt_client_secret,
redirectUri: 'http://localhost'
});
oauthClient.setCredentials({
refresh_token: yt_refresh_token
});
const youtube = google.youtube("v3");
const channelResult = await youtube.channels.list({
auth: oauthClient,
part: ['snippet', 'contentDetails'],
id: [yt_channel_id]
});
let nextPageToken = undefined;
let videosFetched = 0;
do {
const videosResult = await youtube.playlistItems.list({
auth: oauthClient,
maxResults: 50,
pageToken: nextPageToken,
part: ['snippet', 'status'],
playlistId: channelResult.data.items[0].contentDetails.relatedPlaylists.uploads
});
videosFetched += videosResult.data.items.length;
nextPageToken = videosResult.data.nextPageToken;
videosResult.data.items.map((video, index) => {
//process the files as you need to.
});
} while (nextPageToken);
This last .map() function, marked with the "process the files as you need to" comment will receive every video in the channel, whether it be public, unlisted, or private.
NOTE: I do not know yet how long a given refresh_token will last, but assume that you will regularly need to run the first script again and update the refresh_token used via the second script's environment variable.

Error 'Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup' on API call

Trying to make my expressJS app control users' google drive files. I've been utilizing the node 'googleapis' package. After following a slightly dated/incorrect article here, I've gotten the script to:
redirect a user to their authorization url
grab the 'code' from get parameter and then...
register it back as access tokens, which can then be used to...
create a registered 'auth' object
When I use this to create the drive object and try to get it to list files, I get the following error: 'Error: Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup'
This error has already popped up on SO question, and on github.
Following general advice, I re-enabled the drive api, re-downloaded access key. I've also tried replacing the sensitive drive scope a gmail scope, but that didn't work either. I'm not sure where else turn to start debugging at this point. I have a sneaking suspicion my entire auth object is being formed incorrectly but I can't find anything wrong.
This is the related piece of Express app code I'm using to create the authObject and then read drive files.
/**
* Google Utility class that packages different actions related to authentication
*/
class GoogleUtil {
constructor(secretFileName = 'client_secret.json') {
const secret = JSON.parse(fs.readFileSync(secretFileName)).web;
const { client_id, client_secret, redirect_uris } = secret;
this.client_id = client_id;
this.client_secret = client_secret;
this.redirect_uri = redirect_uris[0];
this.standardScope = [
'https://www.googleapis.com/auth/drive',
// 'https://www.googleapis.com/auth/gmail.readonly',
// 'https://www.googleapis.com/auth/userinfo.profile'
];
}
createConnection() {
return new google.auth.OAuth2(this.client_id, this.client_secret, this.redirect_uri); // form authObject
}
getConnectionUrl(auth) {
return auth.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: this.standardScope
});
}
async getAccessTokenFromCode(code) {
var auth = this.createConnection();
const data = await auth.getToken(code);
return data.tokens;
}
}
const g = new GoogleUtil();
/**
* BEGIN ROUTE DEFINITIONS
*/
// THIS IS FIRST STEP. FRONTEND WILL REDIRECT TO GIVEN URL
app.get('/api/googleLoginUrl', async (req, res) => {
const oAuth2Client = g.createConnection();
const url = g.getConnectionUrl(oAuth2Client);
res.json({ url });
});
// *****
// NOTE: THIS IS ROUTE THAT ATTEMPTS TO LIST FILES AND THROWS ERROR
// *****
app.get('/google-auth-redirect', async (req, res) => {
if (!req.query.code) return res.send('Malformed request');
const tokens = await g.getAccessTokenFromCode(req.query.code);
const auth = g.createConnection().setCredentials(tokens);
const drive = google.drive({ version: 'v3', auth: auth });
drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
}, (err, resp) => {
if (err) return console.log('The API returned an error: ' + err);
console.log(resp);
});
res.redirect('/');
});
In the google developer console, clicking on 'create credentials' in the drive API overview informs me that my current credentials are compatible. The project scopes do include ' ../auth/drive'.
I'd want it to be able to list files from an authenticated user's account.
I think this might be related to how you are asking for permissions. If you are using your application to manipulate user's drive files you need a couple of things:
Check you have the correct access scopes setup.
Check you authentication parameter/Oauth screen is setup with said scopes.
You might want to read some documentation regarding authorizing users
Hope this helps! ;)

OAuth2 & Node.js - No redirect after Google confirmation

I'm using Node.js to authenticate my web application with Google+. I've followed the official instructions here. My code looks like this:
var google = require('googleapis');
// OAuth
var OAuth2 = google.auth.OAuth2;
var plus = google.plus('v1');
var oauth2Client = new OAuth2(
'MY_CLIENT_ID', // Client id
'MY_CLIENT_SECRET', // Client secret
'http://localhost:8080/oauth' // Redirect url
);
function getOAuthUrl(){
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/plus.me'
});
return url;
}
// OAuth authorization
app.use('/oauth', function(req, res){
var session = req.session;
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if (!err) {
oauth2Client.setCredentials(tokens);
session['tokens'] = tokens;
res.redirect(__dirname + '/public/html/redirect.html?r=1'); // Success!
}else{
res.redirect(__dirname + '/public/html/redirect.html?r=0'); // Fail!
}
});
});
The login page is called index.html at the root of my folder. The login page makes an ajax request to /oauth/url which responds with the OAuth2 url that the user must click.
JS on index.html:
/* OAuth */
$.ajax({
url: '/oauth/url',
dataType: 'text',
cache: false,
success: function (e) {
$('#login').attr('href', e);
}
});
Node.js response:
// Get OAuth URL
app.use('/oauth/url', function(req, res){
var url = getOAuthUrl();
res.end(url);
});
I can click the link to take me to the authentication page as normal. But when I select the account to authenticate, the page freezes and doesn't get redirected to localhost:8080/oauth like it's supposed to.
UPDATE:
Looking at the networking tab on the console I noticed that the GET request to the callback is being cancelled. The code is recieved by Node.js and so is the request.
Solved:
The issue was with the static directory not allowing Node.js to redirect the page. Fixed by changing the redirect address to /html/redirect.html. Thank you #James.

Getting invalid_request for Youtube Analytics with nodejs library

I am trying to setup nodejs with youtube analytics api. I am currently using a refresh token to try and get access tokens. It works great when using postman but I can't seem to replicate the functionality in nodejs and get a 400: invalid_request with no additional information provided.
Here is my code
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oAuthClient = new OAuth2();
// Retrieve tokens via token exchange explained above or set them:
oAuthClient.setCredentials({
access_token: "",
refresh_token: process.env["YOUTUBE_ANALYTICS_REFRESHTOKEN"]
});
var youtubeAnalytics = google.youtubeAnalytics({
version: 'v1', auth: oAuthClient
});
var moduleExports = {
retrieveDailyBreakdownViews : function(){
var query = {
ids: 'channel==' + {channelID here},
'start-date': '2017-05-01',
'end-date': '2017-05-02',
metrics: 'views,estimatedMinutesWatched',
dimensions: 'insightPlaybackLocationType',
sort: 'views'
}
youtubeAnalytics.reports.query(query, (error, response) => {
console.log(error);
console.log(response);
});
}
}
module.exports = moduleExports;
Any ideas? If this doesn't work I can just try and build the query through HTTP/REST but I'd prefer to use the SDK.
In order to be able to refresh the access token, you will also need the client_id and client_secret. What's happening under the hood is the following request to refresh the token (referenced here):
POST https://accounts.google.com/o/oauth2/token
{
refresh_token: refresh_token,
client_id: this._clientId,
client_secret: this._clientSecret,
grant_type: 'refresh_token'
}
You'll need to initialize your Oauth2 client with :
var oAuthClient = new OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
You'll also need to provide a refresh token that was generated using the same client_id / client_secret if you hardcode the refresh token value
This is what I ended up doing to fix the issue
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(process.env["YOUTUBE_CLIENT_ID"],
process.env["YOUTUBE_CLIENT_SECRET"]);
oauth2Client.credentials.refresh_token =
process.env["YOUTUBE_ANALYTICS_REFRESHTOKEN"];
var youtubeAnalytics = google.youtubeAnalytics({
version: 'v1'
});
I then make my calls like this:
youtubeAnalytics.reports.query(query, (error, response) => {})

how to get people info using google api in node js?

I want to verify user google id on server side.So i want user google info by using google api.I have gone through all documentation but i stuck.I have seen this code,its working fine:
var google = require('googleapis');
var plus = google.plus('v1');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
// Retrieve tokens via token exchange explained above or set them:
oauth2Client.setCredentials({
access_token: 'ACCESS TOKEN HERE',
refresh_token: 'REFRESH TOKEN HERE'
});
plus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
// handle err and response
});
but this is for google plus.I want to fetch user google profile with id not google plus info. Please help.
For googleapis version 19.0.0, you're gonna have to do something like this:
const googleapis = require('googleapis');
googleapis.people('v1').people.get({
resourceName: 'people/me',
personFields: 'emailAddresses,names',
auth: oauth2Client,
(err, response) => {
// do your thing
}
});
The fields resourceName and personFields are the ones that you might get error for saying that these are mandatory. You can find details on these here https://developers.google.com/people/api/rest/v1/people/get.
As for the scopes, following should be enough for this code snippet:
https://www.googleapis.com/auth/userinfo.email and
https://www.googleapis.com/auth/userinfo.profile

Resources