Created
January 3, 2025 19:06
-
-
Save Strajk/956a5ef65da7769a89ba640ef187e7b7 to your computer and use it in GitHub Desktop.
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
// Name: Chrome Profiles | |
// Description: List Chrome profiles and copy their path to clipboard/open in Finder | |
// Author: Strajk | |
import '@johnlindquist/kit' | |
import { Choice } from '@johnlindquist/kit/types'; | |
const chromeAppSupportPaths = [ | |
home('Library/Application Support/Google/Chrome/'), | |
home('Library/Application Support/Google/Chrome Canary/'), | |
home('Library/Application Support/Google/Chrome Dev/'), | |
] | |
let choices: Choice[] = [] | |
for (const chromeAppSupportPath of chromeAppSupportPaths) { | |
if (!await pathExists(chromeAppSupportPath)) continue | |
const subdirs = await readdir(chromeAppSupportPath) | |
for (const subdir of subdirs) { | |
const profilePath = path.join(chromeAppSupportPath, subdir) | |
const profilePreferencesPath = path.join(profilePath, 'Preferences') | |
if (!await pathExists(profilePreferencesPath)) continue | |
try { | |
const preferencesRaw = await readFile(profilePreferencesPath, 'utf-8') | |
const preferencesJson = JSON.parse(preferencesRaw) | |
const useful = pickUsefulFromPreferences(preferencesJson) | |
let title = useful.accountEmail || useful.profileName | |
title += ` (created ${useful.profileCreationTime ? useful.profileCreationTime.toISOString().split('T')[0] : ''})` | |
let description = profilePath | |
let tag = '' | |
if (chromeAppSupportPath.includes('Google/Chrome Canary')) { | |
tag = 'Canary' | |
} else if (chromeAppSupportPath.includes('Google/Chrome Beta')) { | |
tag = 'Beta' | |
} else if (chromeAppSupportPath.includes('Google/Chrome Dev')) { | |
tag = 'Dev' | |
} else if (chromeAppSupportPath.includes('Google/Chrome')) { | |
tag = 'Stable' | |
} | |
let className = '' | |
if (isUnnamedProfile(title)) { | |
className = 'text-gray-400' | |
} | |
choices.push({ | |
value: profilePath, // full path always as a value | |
img: useful.pictureUrl || useful.avatarUrl, | |
name: title, | |
description, | |
tag, | |
nameClassName: className, | |
}) | |
} catch (e) { | |
console.warn(`Error parsing profile at ${profilePreferencesPath}`, e) | |
} | |
} | |
} | |
if (choices.length === 0) { | |
await div("No Chrome profiles found") | |
exit() | |
} | |
// Sort choices: regular profiles alphabetically first, then "Person X" and "Guest" profiles | |
choices.sort((a, b) => { | |
const aIsUnnamed = isUnnamedProfile(a.name) | |
const bIsUnnamed = isUnnamedProfile(b.name) | |
// If both are Unnamed profiles or both are not, sort alphabetically | |
if ((aIsUnnamed && bIsUnnamed) || (!aIsUnnamed && !bIsUnnamed)) { | |
return a.name.localeCompare(b.name) | |
} | |
// Put Unnamed profiles at the end | |
return aIsUnnamed ? 1 : -1 | |
}) | |
let selectedProfile = await arg({ | |
placeholder: 'Select a profile', | |
enter: 'Copy Path', | |
actions: [ | |
{ | |
name: 'Open in Finder', | |
onAction: async (input, { focused }) => { | |
await revealFile(focused.value) | |
}, | |
}, | |
] | |
}, choices) | |
if (selectedProfile) { | |
await clipboard.writeText(selectedProfile) | |
notify(`Copied Profile path to clipboard`) | |
} | |
// HELPERS | |
// === | |
function isUnnamedProfile(title: string) { | |
return title.match(/Person \d+|Guest/) | |
} | |
// Converts Windows FILETIME timestamp (100-nanosecond intervals since January 1, 1601) | |
// to Unix timestamp (seconds since January 1, 1970) | |
// See: https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times | |
function windowsTimestampToDate(windowsTime: number): Date { | |
try { | |
const n = Number(windowsTime) / 1e6 - 11644473600; | |
return new Date(n * 1000); | |
} catch (e) { | |
console.warn(`Error converting Windows timestamp ${windowsTime} to Date`, e); | |
return undefined | |
} | |
} | |
function pickUsefulFromPreferences(preferencesJson: any) { | |
return { | |
accountEmail: preferencesJson.account_info?.[0]?.email, | |
accountName: preferencesJson.account_info?.[0]?.full_name, | |
pictureUrl: preferencesJson.account_info?.[0]?.picture_url, | |
avatarUrl: avatarIdToUrl(preferencesJson.profile?.avatar_index), | |
profileName: preferencesJson.profile?.name, | |
profileCreationTime: windowsTimestampToDate(preferencesJson.profile?.creation_time), | |
lastEngagementTime: windowsTimestampToDate(preferencesJson.profile?.last_engagement_time), | |
extensionsCount: Object.keys(preferencesJson.extensions.install_signature?.ids || {}).length, | |
} | |
} | |
function chromeImageUrl(name: "stable" | "canary" | "dev" | "beta") { | |
let url = `https://raw.githubusercontent.com/chromium/chromium/refs/heads/main/chrome/app/theme/default_100_percent/common/` | |
if (name === "stable") { | |
url += "product_logo_16.png" | |
} else if (name === "canary") { | |
url += "product_logo_32_canary.png" | |
} else if (name === "dev") { | |
url += "product_logo_32_dev.png" | |
} else { | |
url += "product_logo_32_beta.png" | |
} | |
return url | |
} | |
function avatarIdToUrl(avatarId: number) { | |
// from https://gitlab.developers.cam.ac.uk/jz448/browser-android-tabs/-/blob/base-75.0.3770.67-brave-ads/chrome/app/theme/theme_resources.grd | |
const mapIdToFile = { | |
0: 'profile_avatar_generic.png', | |
1: 'profile_avatar_generic_aqua.png', | |
2: 'profile_avatar_generic_blue.png', | |
3: 'profile_avatar_generic_green.png', | |
4: 'profile_avatar_generic_orange.png', | |
5: 'profile_avatar_generic_purple.png', | |
6: 'profile_avatar_generic_red.png', | |
7: 'profile_avatar_generic_yellow.png', | |
8: 'profile_avatar_secret_agent.png', | |
9: 'profile_avatar_superhero.png', | |
10: 'profile_avatar_volley_ball.png', | |
11: 'profile_avatar_businessman.png', | |
12: 'profile_avatar_ninja.png', | |
13: 'profile_avatar_alien.png', | |
14: 'profile_avatar_awesome.png', | |
15: 'profile_avatar_flower.png', | |
16: 'profile_avatar_pizza.png', | |
17: 'profile_avatar_soccer.png', | |
18: 'profile_avatar_burger.png', | |
19: 'profile_avatar_cat.png', | |
20: 'profile_avatar_cupcake.png', | |
21: 'profile_avatar_dog.png', | |
22: 'profile_avatar_horse.png', | |
23: 'profile_avatar_margarita.png', | |
24: 'profile_avatar_note.png', | |
25: 'profile_avatar_sun_cloud.png', | |
26: 'profile_avatar_placeholder.png', | |
27: 'modern_avatars/origami/avatar_cat.png', | |
28: 'modern_avatars/origami/avatar_corgi.png', | |
29: 'modern_avatars/origami/avatar_dragon.png', | |
30: 'modern_avatars/origami/avatar_elephant.png', | |
31: 'modern_avatars/origami/avatar_fox.png', | |
32: 'modern_avatars/origami/avatar_monkey.png', | |
33: 'modern_avatars/origami/avatar_panda.png', | |
34: 'modern_avatars/origami/avatar_penguin.png', | |
35: 'modern_avatars/origami/avatar_pinkbutterfly.png', | |
36: 'modern_avatars/origami/avatar_rabbit.png', | |
37: 'modern_avatars/origami/avatar_unicorn.png', | |
38: 'modern_avatars/illustration/avatar_basketball.png', | |
39: 'modern_avatars/illustration/avatar_bike.png', | |
40: 'modern_avatars/illustration/avatar_bird.png', | |
41: 'modern_avatars/illustration/avatar_cheese.png', | |
42: 'modern_avatars/illustration/avatar_football.png', | |
43: 'modern_avatars/illustration/avatar_ramen.png', | |
44: 'modern_avatars/illustration/avatar_sunglasses.png', | |
45: 'modern_avatars/illustration/avatar_sushi.png', | |
46: 'modern_avatars/illustration/avatar_tamagotchi.png', | |
47: 'modern_avatars/illustration/avatar_vinyl.png', | |
48: 'modern_avatars/abstract/avatar_avocado.png', | |
49: 'modern_avatars/abstract/avatar_cappuccino.png', | |
50: 'modern_avatars/abstract/avatar_icecream.png', | |
51: 'modern_avatars/abstract/avatar_icewater.png', | |
52: 'modern_avatars/abstract/avatar_melon.png', | |
53: 'modern_avatars/abstract/avatar_onigiri.png', | |
54: 'modern_avatars/abstract/avatar_pizza.png', | |
55: 'modern_avatars/abstract/avatar_sandwich.png' | |
} | |
const file = mapIdToFile[avatarId] | |
return `https://raw.githubusercontent.com/chromium/chromium/refs/heads/main/chrome/app/theme/default_100_percent/common/${file}` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment