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.

TLDR show me the code

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 🤷‍♂️

adding bookmarklet to chrome

⚠ 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

See the MDN article for more details

Structure of the bookmarklet

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.

gif of removing videos

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