123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- const got = require('got').default
- const { logout, refreshToken } = require('../index')
- const { withGoogleErrorHandling } = require('../../providerErrors')
- const { prepareStream } = require('../../../helpers/utils')
- const { MAX_AGE_REFRESH_TOKEN } = require('../../../helpers/jwt')
- const logger = require('../../../logger')
- const Provider = require('../../Provider')
- const getBaseClient = ({ token }) => got.extend({
- headers: {
- authorization: `Bearer ${token}`,
- },
- })
- const getPhotosClient = ({ token }) => getBaseClient({ token }).extend({
- prefixUrl: 'https://photoslibrary.googleapis.com/v1',
- })
- const getOauthClient = ({ token }) => getBaseClient({ token }).extend({
- prefixUrl: 'https://www.googleapis.com/oauth2/v1',
- })
- async function paginate(fn, getter, limit = 5) {
- const items = []
- let pageToken
- for (let i = 0; (i === 0 || pageToken != null); i++) {
- if (i >= limit) {
- logger.warn(`Hit pagination limit of ${limit}`)
- break;
- }
- const response = await fn(pageToken);
- items.push(...getter(response));
- pageToken = response.nextPageToken
- }
- return items
- }
- /**
- * Provider for Google Photos API
- */
- class GooglePhotos extends Provider {
- static get authProvider () {
- return 'googlephotos'
- }
- static get authStateExpiry () {
- return MAX_AGE_REFRESH_TOKEN
- }
- // eslint-disable-next-line class-methods-use-this
- async list (options) {
- return withGoogleErrorHandling(GooglePhotos.authProvider, 'provider.photos.list.error', async () => {
- const { directory, query } = options
- const { token } = options
- const isRoot = !directory
- const client = getPhotosClient({ token })
- async function fetchAlbums () {
- if (!isRoot) return [] // albums are only in the root
- return paginate(
- (pageToken) => client.get('albums', { searchParams: { pageToken, pageSize: 50 }, responseType: 'json' }).json(),
- (response) => response.albums,
- )
- }
- async function fetchSharedAlbums () {
- if (!isRoot) return [] // albums are only in the root
- return paginate(
- (pageToken) => client.get('sharedAlbums', { searchParams: { pageToken, pageSize: 50 }, responseType: 'json' }).json(),
- (response) => response.sharedAlbums ?? [], // seems to be undefined if no shared albums
- )
- }
- async function fetchMediaItems () {
- if (isRoot) return { mediaItems: [] } // no images in root (album list only)
- const resp = await client.post('mediaItems:search', { json: { pageToken: query?.cursor, albumId: directory, pageSize: 50 }, responseType: 'json' }).json();
- return resp
- }
- const [sharedAlbums, albums, { mediaItems, nextPageToken }] = await Promise.all([
- fetchSharedAlbums(), fetchAlbums(), fetchMediaItems()
- ])
- const newSp = new URLSearchParams(Object.entries(query));
- if (nextPageToken) newSp.set('cursor', nextPageToken);
- const iconSize = 64
- const thumbSize = 300
- const getIcon = (baseUrl) => `${baseUrl}=w${iconSize}-h${iconSize}-c`
- const getThumbnail = (baseUrl) => `${baseUrl}=w${thumbSize}-h${thumbSize}-c`
- const adaptedItems = [
- ...albums.map((album) => ({
- isFolder: true,
- icon: 'https://drive-thirdparty.googleusercontent.com/32/type/application/vnd.google-apps.folder',
- mimeType: 'application/vnd.google-apps.folder',
- thumbnail: getThumbnail(album.coverPhotoBaseUrl),
- name: album.title,
- id: album.id,
- requestPath: album.id,
- })),
- ...sharedAlbums.map((sharedAlbum) => ({
- isFolder: true,
- icon: 'https://drive-thirdparty.googleusercontent.com/32/type/application/vnd.google-apps.folder',
- mimeType: 'application/vnd.google-apps.folder',
- thumbnail: getThumbnail(sharedAlbum.coverPhotoBaseUrl),
- name: sharedAlbum.title,
- id: sharedAlbum.id,
- requestPath: sharedAlbum.id,
- })),
- ...mediaItems.map((mediaItem) => ({
- isFolder: false,
- icon: getIcon(mediaItem.baseUrl),
- thumbnail: getThumbnail(mediaItem.baseUrl),
- name: mediaItem.filename,
- id: mediaItem.id,
- mimeType: mediaItem.mimeType,
- modifiedDate: mediaItem.creationTime,
- requestPath: mediaItem.id,
- custom: {
- imageWidth: mediaItem.photo ? mediaItem.width : undefined,
- imageHeight: mediaItem.photo ? mediaItem.height : undefined,
- videoWidth: mediaItem.video ? mediaItem.width : undefined,
- videoHeight: mediaItem.video ? mediaItem.height : undefined,
- },
- })),
- ];
- const { email: username } = await getOauthClient({ token }).get('userinfo').json()
- return {
- username,
- items: adaptedItems,
- nextPagePath: newSp.size > 0 ? `${directory ?? ''}?${newSp.toString()}` : null,
- }
- })
- }
- // eslint-disable-next-line class-methods-use-this
- async download ({ id, token }) {
- return withGoogleErrorHandling(GooglePhotos.authProvider, 'provider.photos.download.error', async () => {
- const client = getPhotosClient({ token })
- const { baseUrl } = await client.get(`mediaItems/${encodeURIComponent(id)}`, { responseType: 'json' }).json()
- const url = `${baseUrl}=d`;
- const stream = got.stream.get(url, { responseType: 'json' })
- const { size } = await prepareStream(stream)
- return { stream, size }
- })
- }
- // eslint-disable-next-line class-methods-use-this
- async logout(...args) {
- return logout(...args)
- }
- // eslint-disable-next-line class-methods-use-this
- async refreshToken(...args) {
- return refreshToken(...args)
- }
- }
- module.exports = GooglePhotos
|