Created
January 10, 2026 12:20
-
-
Save pixelspark/71703a23ca6b74817154ce3134cea40b to your computer and use it in GitHub Desktop.
Make albums from folders and set people birth dates from VCF in Immich
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Run: node immich-fill.ts (node v24 or above) | |
| const url = "https://immich-url-here/api/"; | |
| const key = "PASTE API KEY HERE"; | |
| // Paste path to VCF file here (generate it with Apple Contacts -> export) | |
| const vcfPath = "/Users/tommy/Desktop/contacts.vcf"; | |
| import { readFile } from "node:fs/promises"; | |
| async function request(path: string, options?: Record<string, any>) { | |
| let opts = options ?? {}; | |
| if (!opts.headers) opts.headers = {}; | |
| opts.headers["x-api-key"] = key; | |
| const res = await fetch(url + path, opts); | |
| return await res.json(); | |
| } | |
| async function put(path: string, method: string, body: any) { | |
| return request(path, { | |
| method, | |
| headers: { | |
| "Content-type": "application/json", | |
| }, | |
| body: JSON.stringify(body), | |
| }); | |
| } | |
| async function makeAlbums() { | |
| const albums: any[] = await request("albums"); | |
| const paths: string[] = await request("view/folder/unique-paths"); | |
| // Sort albums by name | |
| const albumIdsByName = new Map<string, string>(); | |
| for (const album of albums) { | |
| albumIdsByName.set(album.albumName, album.id); | |
| } | |
| console.log("Existing albums", albumIdsByName); | |
| for (const path of paths) { | |
| if (!path.startsWith("/pictures")) continue; | |
| const parts = path.split("/"); | |
| if (parts.length < 4) continue; | |
| const albumName = parts[3].trim(); | |
| if (!albumIdsByName.has(albumName)) { | |
| // Create album | |
| const res = await put("albums", "POST", { albumName }); | |
| albumIdsByName.set(albumName, res.id); | |
| } | |
| const albumID = albumIdsByName.get(albumName)!; | |
| // Now add all assets in this folder | |
| const assets = await request( | |
| "view/folder?path=" + encodeURIComponent(path) | |
| ); | |
| const ids: string[] = assets.map((x: any) => x.id); | |
| await put("albums/assets", "PUT", { | |
| albumIds: [albumID], | |
| assetIds: ids, | |
| }); | |
| } | |
| console.log("New albums", albumIdsByName); | |
| } | |
| async function setBirthDates() { | |
| const data = (await readFile(vcfPath)).toString("utf8"); | |
| const lines = data.split(/(\r\n|\n|\r)/); | |
| const birthDays = new Map<string, string>(); | |
| let name = null; | |
| for (const line of lines) { | |
| if (line === "BEGIN:VCARD") { | |
| name = null; | |
| continue; | |
| } | |
| const cmd = line.split(/\:/); | |
| if (cmd[0] === "FN") { | |
| name = cmd[1]; | |
| } else if (cmd[0] === "BDAY") { | |
| if (name !== null) { | |
| birthDays.set(name, cmd[1]); | |
| name = null; | |
| } | |
| } | |
| } | |
| const people: any[] = await request("people"); | |
| const peopleIdByName = new Map<string, string>(); | |
| console.log(people); | |
| for (const person of people.people) { | |
| if (person.birthDate) continue; | |
| if (person.name) { | |
| peopleIdByName.set(person.name, person.id); | |
| } | |
| } | |
| for (const [name, birthDay] of birthDays) { | |
| const personID = peopleIdByName.get(name); | |
| console.log(name, birthDay, personID); | |
| if (personID) { | |
| console.log( | |
| "SET BD", | |
| await put("people/" + encodeURIComponent(personID), "PUT", { | |
| birthDate: birthDay, | |
| }) | |
| ); | |
| } | |
| // Let's find the person ID | |
| } | |
| } | |
| // Enable whatever you want to do | |
| setBirthDates(); | |
| //makeAlbums(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment