gphotos-scraper
Version:
A tool to extract public url and metadata from shared album
63 lines (50 loc) • 1.66 kB
text/typescript
import { AlbumNotFoundException } from "../exceptions";
import type { Album } from "../interfaces";
import { getAlbum } from "./getAlbum";
import { getPhotos } from "./getPhotos";
export const extractAlbum = async (albumUrl: string): Promise<Album> => {
const gAlbum = await getAlbum(albumUrl);
if (!gAlbum) throw new AlbumNotFoundException();
const { items, key, id, title, cover, createdAt, updatedAt } = gAlbum;
// Optimized O(1) Lookup Map
const itemsMap = new Map(items.map((item) => [item.id, item.url]));
const album: Album = {
id,
title,
url: albumUrl,
photos: [],
createdAt,
updatedAt,
};
const { default: pLimit } = await import("p-limit");
const MAX_CONCURRENT_REQUESTS = 5;
const limit = pLimit(MAX_CONCURRENT_REQUESTS);
const chunkSize = 500; // Optimal batch size for Google Photos RPC
const tasks = [];
for (let i = 0; i < items.length; i += chunkSize) {
tasks.push(
limit(async () => {
const chunk = items.slice(i, i + chunkSize);
const ids = chunk.map((el) => el.id);
try {
const photosInfo = await getPhotos(key, ids);
return photosInfo.map((el) => {
const url = itemsMap.get(el.id) || "";
return { ...el, url };
});
} catch (err) {
console.error(`Failed to fetch chunk starting at index ${i}`, err);
return []; // Fail gracefully for this chunk
}
}),
);
}
const results = await Promise.all(tasks);
album.photos.push(...results.flat());
// Sorting descending by createdAt
album.photos.sort((a, b) => b.createdAt - a.createdAt);
if (cover) {
album.cover = album.photos.find((photo) => photo.url === cover.url);
}
return album;
};