Last active
December 10, 2024 15:23
-
-
Save knu/80c703b06bf048251da7200eb26c89e6 to your computer and use it in GitHub Desktop.
JXA script to open a URL in a specified Chromium-based browser
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
#!/usr/bin/osascript -l JavaScript | |
// -*- javascript -*- | |
// | |
// open-chromium: Open a URL in a Chromium-based browser in a specified profile | |
// | |
// usage: open-chromium [(-a | --application) <app>] [(-p | --profile) <profile>] <url> | |
// | |
// <app>: "Chromium" (default), "Google Chrome", "Microsoft Edge", etc. | |
// <profile>: "Default", "Profile 1", etc. | |
// | |
// If the URL is already opened in a tab, it is activated. | |
function first(collection) { | |
return collection.length > 0 ? collection[0] : null; | |
} | |
function activateWindowById(app, id) { | |
const window = app.windows.byId(id); | |
if (!window) { | |
console.log("window is gone!"); | |
return false; | |
} | |
if (window.index() !== 1) window.index = 1; | |
return true; | |
} | |
function activateTabById(window, id) { | |
Array.from(window.tabs()).forEach((tab, i) => { | |
if (tab.id() === id) { | |
window.activeTabIndex = i + 1; | |
return true; | |
} | |
}); | |
} | |
function findTabByURL(window, url) { | |
return Array.from(window.tabs()).find( | |
url instanceof RegExp ? (tab) => tab.url().match(url) : (tab) => tab.url() === url | |
); | |
} | |
function selectProfile(app, profile) { | |
const system = Application("System Events"); | |
const browserAppProc = system.applicationProcesses.byName(app.name()); | |
const menuBar = browserAppProc.menuBars[0]; | |
const menu = first( | |
menuBar.menus.whose( | |
{ | |
_or: [ | |
{ name: "Profiles" }, | |
// other languages | |
{ name: "プロファイル" }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
const menuName = menu.name(); | |
const menuItem = first( | |
menu.menuItems.whose( | |
{ | |
_or: [ | |
// Chrome, Chromium | |
{ name: { _endsWith: ` (${profile})` } }, | |
// Microsoft Edge | |
{ name: profile }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
if (!menuItem) return false; | |
const menuItemName = menuItem.name(); | |
menuItem.click(); | |
const isSelected = () => | |
menuBar.menus | |
.byName(menuName) | |
.menuItems.byName(menuItemName) | |
.attributes.byName("AXMenuItemMarkChar") | |
.value() === "✓"; | |
if (isSelected()) return true; | |
for (const id of Array.from(app.windows()) | |
.slice(1) | |
.map((window) => window.id())) { | |
activateWindowById(app, id); | |
if (isSelected()) return true; | |
} | |
return false; | |
} | |
function openUrlInWindow(app, window, url, urlRegExp) { | |
const tab = findTabByURL(window, urlRegExp ?? url); | |
if (tab) { | |
activateTabById(window, tab.id()); | |
} else { | |
const tab = first( | |
window.tabs.whose( | |
{ | |
_or: [ | |
// Chrome, Chromium | |
{ url: "chrome://newtab/" }, | |
// Microsoft Edge | |
{ url: "edge://newtab/" }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
if (tab) { | |
tab.url = url; | |
} else { | |
window.tabs.push(app.Tab({ url })); | |
} | |
} | |
activateWindowById(app, window.id()); | |
} | |
function getWindowInMode(app, mode) { | |
return Array.from(app.windows()).find( | |
(window) => window.mode() === mode | |
) || app.Window({ mode }).make() | |
} | |
function openUrl(app, url, { urlRegExp, profile, mode }) { | |
if (profile) { | |
if (!selectProfile(app, profile)) console.log("profile not found"); | |
if (!url) { | |
activateWindowById(app, getWindowInMode(app, mode).id()); | |
return; | |
} | |
openUrlInWindow(app, getWindowInMode(app, mode), url, urlRegExp); | |
} else { | |
if (!url) { | |
activateWindowById(app, getWindowInMode(app, mode).id()); | |
return; | |
} | |
Array.from(app.windows()).find((window, i) => { | |
if (window.mode() !== mode) return false; | |
const tab = findTabByURL(window, urlRegExp ?? url); | |
if (tab) { | |
activateWindowById(app, window.id()); | |
activateTabById(window, tab.id()); | |
return true; | |
} | |
return false; | |
}) || openUrlInWindow(app, getWindowInMode(app, mode), url, urlRegExp); | |
} | |
} | |
function urlPatternToRegExp(pattern) { | |
const re = pattern.replace(/\\(.)|(\*\*)|(\*)|([$()+.?[\]^{|}])|(.)/g, (_, esc, dstar, star, meta, safe) => { | |
if (esc) return esc; | |
if (dstar) return ".*"; | |
if (star) return "[^/]*(?:\\?.*)?"; | |
if (meta) return `\\${meta}`; | |
return safe; | |
}); | |
return new RegExp(`^${re}$`); | |
} | |
function run(argv) { | |
let appName = "Chromium", | |
profile, | |
urlRegExp, | |
mode = "normal", | |
urls = []; | |
for (let i = 0; i < argv.length; i++) { | |
const arg = argv[i]; | |
let m; | |
if ((m = /^(?:-p(.+)?|--profile(?:=(.*))?)$/.exec(arg))) { | |
profile = m[1] ?? m[2] ?? argv[++i]; | |
} else if ((m = /^(?:-a(.+)?|--application(?:=(.*))?)$/.exec(arg))) { | |
appName = m[1] ?? m[2] ?? argv[++i]; | |
} else if ((m = /^(?:-m(.+)?|--match(?:=(.*))?)$/.exec(arg))) { | |
urlRegExp = urlPatternToRegExp(m[1] ?? m[2] ?? argv[++i]); | |
} else if (arg === "--incognito" || arg === "-i") { | |
mode = "incognito"; | |
} else { | |
urls.push(arg); | |
} | |
} | |
if (urls.length === 0) urls.push(null); | |
const app = Application(appName); | |
app.activate(); | |
for (const url of urls) { | |
openUrl(app, url, { urlRegExp, profile, mode }); | |
} | |
app.activate(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment