Does anyone have an example of how to use the google photos API in 2023? I'm specifying the year because if I try to search for documentation for this, I end up on a photos.get (from google) with sample code that when I run it, gives me an error that the documented approach is already deprecated:
"You have created a new client application that uses libraries for user
authentication or authorization that will soon be deprecated. New clients must
use the new libraries instead; existing clients must also migrate before these
libraries are deprecated. See the [Migration
Guide](https://developers.google.com/identity/gsi/web/guides/gis-migration) for
more information."
I'm trying to figure out how to use this API endpoint in Javascript to access the contents of a public Google Photos album:
https://photoslibrary.googleapis.com/v1/albums/{albumId}
It seems for public album this should be fairly straight-forward. But so far it looks like oauth is needed, even to access something like a public photo album.
Is it really this hard?
The issue you are having is that the sample you are using is from the old JavaScript signin/authorization library.
Google has split that up now you need to use Authorizing for Web as you can guess google has a lot of samples to be updated. I doubt they have updated everything.
I have a QuickStart created. Make sure that you create web application credentials on google developer console and create an api key.
<!DOCTYPE html>
<html>
<head>
<title>Photos API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Photos API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
/* exported gapiLoaded */
/* exported gisLoaded */
/* exported handleAuthClick */
/* exported handleSignoutClick */
// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = '[REDACTED]';
const API_KEY = '[REDACTED]';
// Discovery doc URL for APIs used by the quickstart
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest';
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/photoslibrary.readonly';
let tokenClient;
let gapiInited = false;
let gisInited = false;
document.getElementById('authorize_button').style.visibility = 'hidden';
document.getElementById('signout_button').style.visibility = 'hidden';
/**
* Callback after api.js is loaded.
*/
function gapiLoaded() {
gapi.load('client', initializeGapiClient);
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function initializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
}
/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
document.getElementById('authorize_button').style.visibility = 'visible';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
document.getElementById('signout_button').style.visibility = 'visible';
document.getElementById('authorize_button').innerText = 'Refresh';
await listAlbums();
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
document.getElementById('authorize_button').innerText = 'Authorize';
document.getElementById('signout_button').style.visibility = 'hidden';
}
}
/**
* Print metadata for first 10 Albums.
*/
async function listAlbums() {
let response;
try {
response = await gapi.client.photoslibrary.albums.list({
'pageSize': 10,
'fields': 'albums(id,title)',
});
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const albums = response.result.albums;
if (!albums || albums.length == 0) {
document.getElementById('content').innerText = 'No albums found.';
return;
}
// Flatten to string to display
const output = albums.reduce(
(str, album) => `${str}${album.title} (${album.id}\n`,
'albums:\n');
document.getElementById('content').innerText = output;
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>
</html>
public album.
The thing is you say your after public albums, if the album is in fact public then you should be able to get away with just using the api key. You should only need to be authorized if its private user data.
Update: after a bit of digging im not even sure this library will allow you to only login with an api key with out sending a valid client id. I have sent an email off to the Identity team to see if they have something they can share.
Update:
I got a message back from the team:
A client ID is required even to access content the user has made available to everyone.
So even if the data is public you will have to register a client id and request permissions of the user.
Related
I want to use access token instead of authorization. I have get my token and stored it in local storage. Now I am saying, if user has token then do not need of authorization. just call the required method. But it says "API keys are not supported by this API. Expected OAuth2 access token or other authentication credentials that assert a principal. See https://cloud.google.com/docs/authentication". How can I use access token in every Gmail method calling. Like for adding label, getting email messages in my DOM and all this stuff.
In short, I wanna get rid of every time authentication.
Normally with authorization flow we use something called a refresh token to enable applications to request a new access token when the user is offline or the user returns after being away from the application. This is not possible with implicit flow.
Implicit flow is defined in The OAuth 2.0 Authorization Framework
The implicit grant type is used to obtain access tokens (it does not
support the issuance of refresh tokens) and is optimized for public
clients known to operate a particular redirection URI. These clients
are typically implemented in a browser using a scripting language
such as JavaScript.
Client side JavaScript uses implicit flow there is no other option, the user will need to authorize your application every time they want to use it.
Official example.
The official Gmail JavaScript quick start will walk you though how to access this api. The client library handles all the interaction between your application and the authorization server for you so you wont need to worry about where to apply the access token.
<!DOCTYPE html>
<html>
<head>
<title>Gmail API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Gmail API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
/* exported gapiLoaded */
/* exported gisLoaded */
/* exported handleAuthClick */
/* exported handleSignoutClick */
// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = '<YOUR_CLIENT_ID>';
const API_KEY = '<YOUR_API_KEY>';
// Discovery doc URL for APIs used by the quickstart
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest';
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
let tokenClient;
let gapiInited = false;
let gisInited = false;
document.getElementById('authorize_button').style.visibility = 'hidden';
document.getElementById('signout_button').style.visibility = 'hidden';
/**
* Callback after api.js is loaded.
*/
function gapiLoaded() {
gapi.load('client', initializeGapiClient);
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function initializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
}
/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
document.getElementById('authorize_button').style.visibility = 'visible';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
document.getElementById('signout_button').style.visibility = 'visible';
document.getElementById('authorize_button').innerText = 'Refresh';
await listLabels();
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
document.getElementById('authorize_button').innerText = 'Authorize';
document.getElementById('signout_button').style.visibility = 'hidden';
}
}
/**
* Print all Labels in the authorized user's inbox. If no labels
* are found an appropriate message is printed.
*/
async function listLabels() {
let response;
try {
response = await gapi.client.gmail.users.labels.list({
'userId': 'me',
});
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const labels = response.result.labels;
if (!labels || labels.length == 0) {
document.getElementById('content').innerText = 'No labels found.';
return;
}
// Flatten to string to display
const output = labels.reduce(
(str, label) => `${str}${label.name}\n`,
'Labels:\n');
document.getElementById('content').innerText = output;
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>
</html>
First of all, "I'm noob"
My first time running some script on a cloud server.
So, I have a code that counts the number of views from my youtube vídeo and writes it in the title.
But I want that code running 24/7.
I make it works running on a VM instance on google cloud using cron-node running the code every 5 minutes and make it run forever with "forever" on the node.
And my question is: Have a better way to run it? Like using cloud functions or something? Or I should still run it on VM instance?
If yes, what should I do? Because I don't know how to use external dependencies on Cloud function like google API, if I don't have a console to install googleapi dependencies. (Maybe it's easy to make it, but I just don't know yet).
var readline = require('readline');
var { google } = require('googleapis');
var OAuth2 = google.auth.OAuth2;
const cron = require("node-cron");
cron.schedule("*/5 * * * *", () => {
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'update-youtube-title.json';
const youtube = google.youtube('v3');
const video_id = 'f0ARwVVxoBc';
// Load client secrets from a local file.
fs.readFile('credentials.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the YouTube API.
authorize(JSON.parse(content), makeAuthCall);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function (code) {
rl.close();
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) throw err;
console.log('Token stored to ' + TOKEN_PATH);
});
}
/**
* Lists the names and IDs of up to 10 files.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
/*
function getChannel(auth) {
var service = google.youtube('v3');
service.channels.list({
auth: auth,
part: 'snippet,contentDetails,statistics',
forUsername: 'GoogleDevelopers'
}, function (err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var channels = response.data.items;
if (channels.length == 0) {
console.log('No channel found.');
} else {
console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
'it has %s views.',
channels[0].id,
channels[0].snippet.title,
channels[0].statistics.viewCount);
}
});
}
*/
//código novo
const makeAuthCall = (auth) => {
//get views
youtube.videos.list(
{
auth: auth,
id: video_id,
part: 'snippet,statistics'
},
(err, response) => {
if (err) {
console.log(`A contagem de views falhou ${err}`)
return;
}
if (response.data.items[0]) {
//atualização encontrada
console.log('Encontramos uma atualização...');
updateVideoTitle(response.data.items[0], auth);
}
}
);
};
const updateVideoTitle = (video, auth) => {
let viewsText = video.statistics.viewCount.toLocaleString();
const newTitle = `Esse Vídeo tem ${viewsText} Visualizações`;
const oldTitle = video.snippet.title;
if (newTitle != oldTitle){
video.snippet.title = newTitle;
console.log(`Atualizando contagem de views para ${viewsText}`);
youtube.videos.update(
{
auth: auth,
part: 'snippet,statistics',
resource: video,
},
(err, response) => {
console.log(response)
if (err) {
console.log(`A contagem de views falhou ${err}`);
return;
}
if (response.data.items) {
console.log("Done");
}
}
);
}else{
console.log("Atualização não necessária");
};
}
})
This is a very broad question with too much to unpack. Keep in mind the differences below are very simplistically explained and there could be more steps.
Compute Engine/VM:
Step 1. Install javascript runtime (Nodejs) on the OS.
Step 2. Put your code & dependencies
Cloud Functions:
Step 1. Put your code & dependencies
Summary:
Cloud Functions removes the need to manage the operating system and
runtime. It might be easier for you. One way or another, you won't
escape the need for your dependency setup.
For Practice:
These "quick start" articles will give an understanding of the setup process. You learn by doing with a "hello world" example.
For Dependencies:
There is an article here that shows how to declare them in Cloud Functions. The sections of this article explains how you can load dependencies in different ways. The first example says: "The dependency is then imported in the function" and you see the part that says "require('escape-html')", it's the same as the "escape-html": "^1.0.3". This means that you can work backwards and see the requirements in your code to add them to your dependency manifest. But you may have to learn a bit more about the syntax by doing a Google search about package.json/npm and how it works in general.
For Authentication:
Assuming your code is properly written, the "require('googleapis')" at the top of your code means that you should only need to add dependencies like mentioned previously and your code may be good to go for authentication.
For Cron jobs:
In your code you are doing that by using "const cron = require("node-cron");" at the top of you code. This is like everything else I mentioned about dependencies and it's a software that triggers your code. I'm not sure if this will work like all other ones because Google has its own Cron jobs that work like this (outside the Cloud Functions environment, but triggers the Cloud Functions on the network). Keep in mind that this method could alter your code significantly. So if you can test with const cron = require("node-cron"); dependencies, then do that first.
Disclaimer:
Please open more specific questions if you can next time. Stackoverflow is not for consultation or broad questions with many follow-ups. It's more for coding answers to specific questions. Some may decide to close this post due to that.
Hope this helps.
I would like to use the google iot core api from a firebase function.
It all works, but it is very slow. I think is due to the authentication process that needs to be carried out one very call. Is there a way to speed this up?
Right now I have this:
function getClient(cb) {
const API_VERSION = 'v1';
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
google.discoverAPI(discoveryUrl, {}).then( end_point => {
cb(end_point);
});
}
And this allows me to do:
export function sendCommandToDevice(deviceId, subfolder, mqtt_data) {
const cloudRegion = 'europe-west1';
const projectId = 'my-project-id;
const registryId = 'my-registry-id';
getClient(client => {
const parentName = `projects/${projectId}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;
const binaryData = Buffer.from(mqtt_data).toString('base64');
const request = {
name: `${registryName}/devices/${deviceId}`,
binaryData: binaryData,
subfolder: subfolder
};
client.projects.locations.registries.devices.sendCommandToDevice(request,
(err, data) => {
if (err) {
console.log('Could not update config:', deviceId);
}
});
});
}
The way that I've found to speed it up is to avoid doing the authentication. I've solved it doing this:
const google = new GoogleApis();
const API_VERSION = 'v1';
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
var googleClient;
google.discoverAPI(discoveryUrl, {}).then( client => {
//cb(end_point);
googleClient = client;
});
// Returns an authorized API client by discovering the Cloud IoT Core API with
// the provided API key.
function getClient(cb) {
cb(googleClient);
}
But when happens then when the client expires? Is there any good solution from using google apis from firebase functions?
The problem may be the discovery pieces. There's a direct IoT Core admin REST API, so you don't have to use discovery...I think. I haven't worked with the Firebase Functions, but they're roughly equivalent to the Google Cloud Functions which may end up working here also. The code we (in a live demo we did) ran to do what you're doing is here if you wanted to tinker around and see if you can get this running in a Firebase Function.
How I can use Swagger-generated API client source on client-site (normal browser application without NodeJs)?
In a first test I generated a javascript client for Swaggers' petstore API (https://petstore.swagger.io/v2) using editor.swagger.io
The generated code is containing a index.js which provides access to constructors for public API classes, which I try to embed and use in my web application.
The documentation describes the usage of the API like so:
var SwaggerPetstore = require('swagger_petstore');
var defaultClient = SwaggerPetstore.ApiClient.instance;
// Configure API key authorization: api_key
var api_key = defaultClient.authentications['api_key'];
api_key.apiKey = 'YOUR API KEY';
// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
//api_key.apiKeyPrefix = 'Token';
var apiInstance = new SwaggerPetstore.PetApi();
var petId = 789; // Number | ID of pet to return
var callback = function(error, data, response) {
if (error) {
console.error(error);
} else {
console.log('API called successfully. Returned data: ' + data);
}
};
apiInstance.getPetById(petId, callback);
This works fine for NodeJs applications. But how I can use the API for conventional client-site web-apps inside the browser? For such applications the nodejs function require does not work.
From https://github.com/swagger-api/swagger-js
The example has cross origin problem, but it should work in your own project
<html>
<head>
<script src='//unpkg.com/swagger-client' type='text/javascript'></script>
<script>
var specUrl = '//petstore.swagger.io/v2/swagger.json'; // data urls are OK too 'data:application/json;base64,abc...'
SwaggerClient.http.withCredentials = true; // this activates CORS, if necessary
var swaggerClient = new SwaggerClient(specUrl)
.then(function (swaggerClient) {
return swaggerClient.apis.pet.addPet({id: 1, name: "bobby"}); // chaining promises
}, function (reason) {
console.error("failed to load the spec" + reason);
})
.then(function(addPetResult) {
console.log(addPetResult.obj);
// you may return more promises, if necessary
}, function (reason) {
console.error("failed on API call " + reason);
});
</script>
</head>
<body>
check console in browser's dev. tools
</body>
</html>
So I am trying to send a notification via Functions on Firebase.
I am doing the notification programming on JavaScript via Node.js.
Upon clicking Send Friend Request from one account, the other person is suppose to get a notification as specified on the payload of the JavaScript file I have attached below.
I keep getting the following error on my Firebase Functions
ReferenceError: event is not defined.
Here is an image of the exact error.
Here is my JavaScript file:
/*
* Functions SDK : is required to work with firebase functions.
* Admin SDK : is required to send Notification using functions.
*/
//This runs JavaScript in Strict Mode, which prevents the use of things such as undefined variables.
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/*
* 'OnWrite' works as 'addValueEventListener' for android. It will fire the function
* everytime there is some item added, removed or changed from the provided 'database.ref'
* 'sendNotification' is the name of the function, which can be changed according to
* your requirement
*/
exports.sendNotification = functions.database.ref('/Notifications/{retrieveUserId}/{notificationId}').onWrite((data, context) => {
/*
* You can store values as variables from the 'database.ref'
* Just like here, I've done for 'user_id' and 'notification'
*/
const retrieveUserId = context.params.retrieveUserId;
const notificationId = context.params.notificationId;
console.log('User id is : ', retrieveUserId);
//Prevents notification being sent if there are no logs of notifications in the database.
if (!event.data.val()) {
return console.log('A notification has been deleted from the database : ', notificationId);
}
const deviceToken = admin.database().ref(`/Users/${retrieveUserId}/device_token`).once('value');
return deviceToken.then(result => {
const tokenId = result.val();
const payload = {
notification: {
title: "Friend Request",
body: "You have received a friend request from Slim Shady",
icon: "default"
}
};
return admin.messaging().sendToDevice(tokenId, payload).then(response => {
return console.log('Notification was sent to the user');
});
});
});
This is a picture of parents and children of my Firebase database referred to in the JavaScript file.
As the error states an event not being defined, I'm trying to figure out which event I have not defined.
What is the issue here?
You have not defined event in this code block:
if (!event.data.val()) {