As a follow-up or generalisation of an earlier (Google and Safari-specific) macro, here is a draft macro which aims for a bit more flexibility by working with either:
- Chrome or Safari, and
- DuckDuckGo or Google search.
You can specify whether you want it to:
- open a new tab for each search result,
- copy all search results as Markdown links
- limit the maximum number of results that are opened or copied.
Ver 0.2 (Re-enabled blnOpenTabs option – disabled during testing)
First N DuckDuck or Google search results (Safari or Chrome).kmmacros (30.0 KB)
Javascript for Automation source
(() => {
'use strict';
// Ver 0.2
// removed `|| true` after blnOpenTabs
const main = () => {
const
kme = Application('Keyboard Maestro Engine'),
blnOpenTabs = Boolean(eval(
kme
.getvariable('openResultTabs')
)),
intMaxLinks = parseInt(
kme.getvariable('maxResultTabs'),
10
) || 10,
xpathDuck = "//*[@class='result__a']",
xpathGoogle = "//*[@class='r']/a";
return bindLR(
frontAppNameLR(),
strApp => elem(
strApp, ['Safari', 'Google Chrome']
) ? (() => {
const
mbLinks = foldl(
(a, xpath) => a.Nothing ? (
xPathHarvestMay(
strApp,
blnOpenTabs,
xpath,
intMaxLinks
)
) : a, Nothing(), [xpathDuck, xpathGoogle]
);
return mbLinks.Nothing ? (
Left(
'No DuckDuck or Google result links on this page.'
)
) : Right(mbLinks.Just);
})() : Left('Neither Safari nor Google Chrome at front')
);
};
// CHROME AND SAFARI ---------------------------------------
const xPathHarvestMay = (appName, blnOpenTabs, strXPath, intMax) => {
const
browser = Application(appName),
ws = browser.windows;
return bindMay(
0 < ws.length ? (
Just(ws.at(0))
) : Nothing(),
w => {
const
xs = take(
intMax,
pageXPathHarvest(
browser, w, strXPath
)
);
return 0 < xs.length ? (
// Optional browser effect
blnOpenTabs && tabsOpened(browser, w, xs),
// Keyboard Maestro value
Just(
xs
.reduce(
(a, link) =>
`${a}[${link[0]}](${link[1]})\n`,
''
)
)
) : Nothing();
}
);
};
// Harvest elements from Safari or Chrome by XPath pattern
const pageXPathHarvest = (browser, oWin, strXPath) => {
const strApp = browser.name();
return 'Safari' === strApp ? (
browser.doJavaScript(
`(${xpathHarvest})("${strXPath}")`, { in: oWin.currentTab
}
)
) : strApp.startsWith('Google') ? (
browser.execute(oWin.activeTab(), {
javascript: `(${xpathHarvest})("${strXPath}")`
})
) : [];
};
// tabsOpened :: Application -> Window -> (String, String) -> IO()
const tabsOpened = (browser, oWin, links) => {
const winTabs = oWin.tabs;
links.forEach(
link => winTabs.push(
Object.assign(
browser.Tab(), {
url: link[1]
}
)
)
);
};
// Harvesting function to run in the browser context
const xpathHarvest = strPath => {
const
r = document.evaluate(
strPath, document, null, XPathResult.ANY_TYPE, null
),
xs = [];
var oNode;
while (oNode = r.iterateNext()) {
xs.push([oNode.text, oNode.href]);
}
return xs;
};
// GENERIC FUNCTIONS ----------------------------------
// https://github.com/RobTrew/prelude-jxa
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// Just :: a -> Just a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
// Nothing :: () -> Nothing
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
const bindMay = (mb, mf) =>
mb.Nothing ? mb : mf(mb.Just);
// elem :: Eq a => a -> [a] -> Bool
const elem = (x, xs) => xs.includes(x);
// foldl :: (a -> b -> a) -> a -> [b] -> a
const foldl = (f, a, xs) => xs.reduce(f, a);
// showLog :: a -> IO ()
const showLog = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
// take :: Int -> [a] -> [a]
const take = (n, xs) => xs.slice(0, n);
// JXA ------------------------------------------------
// frontAppNameLR :: () -> Either String String
const frontAppNameLR = () => {
const
xs = Application('System Events')
.applicationProcesses.where({
frontmost: true
});
return 0 < xs.length ? (
Right(xs[0].name())
) : Left('No frontmost application found');
};
// MAIN ---
return main();
})();