Skip to content
Advertisement

Using google photos API in 2023

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?

Advertisement

Answer

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.

Advertisement