Bookmarklets - Automating Websites
Published on Monday, September 6th, 2021
Every so often I find myself on a website (like YouTube) where I wish it had X or Y feature. Bookmarklets are powerfull little tools you can use to create automation against websites you use.
Bookmarklets
Bookmarklets are a piece of custom JavaScript run from the bookmark's UI in your browser on the current page. They are a bookmark that points to javascript instead of a URL. Think lo-fi proto browser plugin 🤷♂️
⚠ WARNING ⚠ Make sure you understand whatever is in a bookmarklet, this code is running AS YOU against websites that you may be logged into (like a bank, social media, etc). A nefarious bookmarklet author could do bad things to you.
Things that can be done with a bookmarklet
- Poke authenticated api endpoints
- Drive UI like clicking
- Manipulate the DOM
- Malware...😱
See the MDN article for more details
Structure of the bookmarklet
- Prefixed with
javascript:
with some code to execute for example:javascript:(function(){alert('hello world');})()
- Must not contain newlines
Example "hello world" bookmarklet
javascript:(function(){alert('hello world');})()
YouTube playlist managment (at the time of writing this)
One feature I wish YouTube had was to remove videos automatically that are at a certain percentage watched. Currently the "Remove Watched" removes any video with any amount of watch time 😢 this is no good for me because sometimes I watch a few seconds. I want to remove videos at 80 or maybe 90 percent watched.
Websites change over time, and anything you write to automate them will decay, the code in this blog post is based on a reddit post that doesn't work anymore, but I've updated it for August 2021 version of YouTube (also the source is on github)
/* Unprocessed script for removing videos */
var removeVids = function () {
/* Ask user for number of videos to remove */
var n = parseInt(prompt('Number of videos to remove?', 1));
/* What is the "watched percent" */
var watchedPercent = parseInt(prompt('Percent to consider watched', 100));
/* Find the videos that match that percentage based on the width of the progress element */
var vids = Array.from(document.querySelectorAll('ytd-playlist-video-renderer'));
var vidsThatMatch = vids.filter(v => {
return v.querySelectorAll('#progress').length && parseInt(v.querySelector('#progress').style.width) >= watchedPercent;
});
/* Janky loop based on timing since we are driving UI we need to give it time to respond*/
if (vidsThatMatch.length < n) { n = vidsThatMatch.length };
var i = 0;
var interval = setInterval(function () {
if (n > 0) {
/* open the video context menu */
vidsThatMatch[i].querySelector('yt-icon-button.ytd-menu-renderer').click();
var title = vidsThatMatch[i].querySelector('#video-title');
console.log('Removed:', title.innerText, title.href);
/* Need to search the whole document pop up lives in another container in the DOM */
setTimeout(() => {
/* Some xpath to find the remove from playlist dialog in the DOM */
var removeResults = document.evaluate("//span[contains(., 'Remove from')]", document, null, XPathResult.ANY_TYPE, null);
var removeText = removeResults.iterateNext();
removeText.click();
n--;
i++;
});
} else {
console.log('No vids to remove');
clearInterval(interval);
}
}, 500);
};
removeVids();
Building Bookmarklets with Webpack
Depending on how complicated your bookmarklet becomes, manually crafting the javascript:...
might become too onerous. It is possible to use a bundler like webpack to help out, this also suggests the possiblility of really rich bookmarklet experiences!
Transforming the source to match the bookmarklet isn't too bad, based on this SO post I've crafted a webpack configuration.
I tweaked the webpack plugin in the original post to be compatible with webpack v5
// Modified for webpack v5: See https://stackoverflow.com/a/46920791/839595
class AssetToBookmarkletPlugin {
pluginName = 'AssetToBookmarkletPlugin';
apply(compiler) {
compiler.hooks.thisCompilation.tap(this.pluginName, (compilation) => {
compilation.hooks.processAssets.tap({
name: this.pluginName,
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
}, (assets) => {
// Emit a new .bookmarklet
for (const assetName in assets) {
const asset = assets[assetName];
const content = 'javascript:' + encodeURIComponent('(function(){' + asset.source() + '})()');
compilation.emitAsset(assetName + '.bookmarklet', new webpack.sources.RawSource(content))
}
});
});
};
}
Tada! 🎉
Here is the source code for the bookmarklet.
(Remember to verify any bookmarklets you decide to trust) Click and drag this link into your bookmarks bar to Remove Videos By Percent
Hope this helps anyone wanting to do some bookmarklet automation! -Erik
Help support me on Github Sponsors or Patreon
Become a Patron!