HamsterScript - a micro-library for TamperMonkey
Tags: tampermonkey, hamsterscript
Published: 2022-03-14 01:29:40

---
title: HamsterScript - a micro-library for TamperMonkey
published: true
description: Creating a micro-library for personal use
tags: TamperMonkey,HamsterScript
//cover_image: https://direct_url_to_image.jpg
---

The following is just some thoughts and findings I've had on creating a micro-library for personal use for TamperMonkey and Userscripts. 

The library is not published yet as it is still a proof of concept and a little messy currently, meaning the API isn't very stable


## TamperMonkey Intro

Something I've recently started goofing around with is TamperMonkey, a browser extension that allows you to "install and create userscripts, which are JavaScript programs that can be used to modify web pages". 

I enjoy automating and hacking things together and TamperMonkey scratches that itch for me. Browser extensions are very powerful but sometimes you just need to restyle a webpage or inject a little bit of JS. A few scripts I've created recently that I found myself using daily are:

- Display area stats for properties on Rightmove (i've been looking at houses a lot lately)
- Add a hyperlink on every GitHub PR to a "short lived environment" & a link to JIRA tickets based on ticket ID found in the branch name (assuming one exists)
- Autopopulating web forms for testing systems (browser extensions like iMacros do exist but can be very limited)
- Enrich websites I'm currently developing with detailed debugging information (e.g a toolbar to display Session info, the current git commit_ref, links to Kibana for the logged in user, cloud tools, links to CMS and CRM entries, envconfigs at a glance, all sorts reall). Can be very useful for less technical people too such as testers and product owners so they can diagnose issues on dev environments.


## Why Create A Library Though?

I find myself constantly copy/pasting boilerplate code between userscripts or re-writing from memory and introducing bugs. Want to insert an element, then you'll need a lot of lines of code for this:
```
var textEl = document.createElement('p')
textEl.innerText = "foo";
textEl.className = "";
textEl.onclick = populate;
logo.appendChild(textEl);
// or
someOtherElement.parentNode.appendChild();
// or this monstrosity
textEl.insertBefore(someOtherElement, textEl.firstChild);
// or there's other ways to do this!
```

The same applies for making fetch requests too. The browsers built in `fetch` function blocks most requests due to CORS. So you'll need to use `GM_xmlhttpRequest` instead. But that doesn't use promises because xmlhttpRequest is ancient. So you'll probably want to wrap that in a Promise. Then you'll need to parse the response. Then some other stuff. I've needed to copy/paste something like this and change bits for multiple scripts now:

```
// The following userscript @grant is required to make cross origin requests:
// @grant GM_xmlhttpRequest

async function parseWebsite(postcode) {
    const url = `https://www.ilivehere.co.uk/check-my-postcode/${postcode}`;

    const response = await Request(url); // Request is defined below
    const html = response.responseText;
    var parser = new DOMParser();

    // Parse the text
    var doc = parser.parseFromString(html, "text/html");
    const number = doc.querySelector('.ilivehere-rating-number').textContent;
    return number;
}

// Wrap xmlRequest in a Promise
function Request(url, opt={}) {
    Object.assign(opt, {
        url,
        timeout: 2000,
        responseType: 'json'
    })

    return new Promise((resolve, reject) => {
        opt.onerror = opt.ontimeout = reject
        opt.onload = resolve

        GM_xmlhttpRequest(opt)
    })
}
```


Another example - reading a cookie. Unfortunately there's no `document.readCookie("foo")` so I find myself copy/pasting this and other 'utility' functions across Userscripts:
```
const getCookie = (name)=> {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

```

## HamsterScript Intro

Assuming you've made it this far, thanks for sticking around! I decided to create `HamsterScript` for two reasons:
- Make it slightly easier to create future Userscripts by using a library with a set of easy to use, common helper functions
- Create a well documented "cheatsheet" for personal Userscript knowledge

## HamsterScript Usage

Installing a library is really simple in a Userscript. Heck, you could even include frameworks like Vue or jQuery if you wanted or libraries like D3 or your own raw JS files hosted on a CDN such as github!

```
// @name         [HamsterScript Example]
...
// @grant        GM_xmlhttpRequest
// @require      <placeholder>/hamsterscript-latest.min.js
...
(async function() {
    'use strict';

    const doc = await hamster.fetchDom("<some-url>");
    const stats = doc.querySelector('.body-band-highlight').childNodes[1].textContent;
        const newBtn = hamster.insert({tag: "p", text}, `[itemprop="streetAddress"]`);
})

```
Only 3 lines of code for one of my most common use cases for Tampermoney. The same in vanilla JS is 20+ lines. Understandably you may lose some readability (what does hamster.fetchDom actually do?). Well as I previously mentioned, one goal of this library is to create a "cheatsheet" along with abstracting common functions into a reusable library! You can see what `hamster.fetchDom` does in the HamsterScript docs OR view the annotated code on GitHub, along with a full explanation and alternative methods


## Findings
I doubt anybody will ever come across or use this framework or choose to use it but it's been a fun little exercise for a few reasons:

- Learnt JSDoc and generating documentation
- Found out about multiple TamperMonkey APIs and other scripts like `waitForKeyElements`
- Comparing the "before" and "after" of using HamsterScript (in one case, 95 lines was simplified to 55 lines)
- Creating pipelines from scratch to minify and deploy my library

## Future Steps
As this is only a prototype, there's definitely a lot of improvements I could add in the future:

- Think about versioning my library 
- Hosting multiple versions on a CDN along with multiple versions of documentation
- Prevent breaking changes
- Improve minification pipelines
- Add HamsterScript (and general TamperMonkey) examples 
- Create a testing pipeline