Botmation Documentation

Overview

Overview

Botmation is a simple, declarative framework to build bots with composable functions called BotActions.

β€œEverything should be made as simple as possible, but no simpler.”

~ Albert Einstein

Orange Bot

BotActions are self-reliant, async functions that perform bot tasks from specific things such as "click this button", to broad flows such as "scrape this feed and like my friends' posts". They can be ran by themselves or composed together as an assembly of diverse functions that handle complex tasks. They are like bricks, you can lay them down to build walls, then use the walls to build buildings and so forth. Except, at each level of composition (brick, wall, building, etc), they are still considered the same type of thing. Therefore, they can be mixed and used anywhere, regardless of their compositional complexity.

Imagine a door πŸšͺ into a coffee shop β˜• where customers 🧍,🧍,🧍 enter. As they step inside they form a line 🧍🧍🧍 to place their orders. The first customer served, is the first customer in line, then the following, and so forth.

Botmation's bots are assembled in lines of BotActions. The bots run the actions in the order declared, from first to last.

However, a single BotAction can actually be a composition of a bunch of other BotActions. Therefore, a single person 🧍 in the coffee shop β˜•, can actually be a whole other line of people 🧍🧍🧍. Then any one of those people 🧍 in this "sub" line 🧍🧍🧍, can be a whole other line of people 🧍🧍🧍, and infinitely deep β™ΎοΈπŸ‡.

The possibilities are endless!

Running BotActions

BotActions run on a Puppeteer page instance and are completed by resolving their function's returned Promise.

If you're unfamiliar with async functions in JavaScript, they are simply functions that return Promises. Promises are a way to handle async functionality like reacting to events, when you don't know when they'll take place. This is important since a lot of Puppeteer's API is asynchronous.

Some BotActions are have higher-order functions to customize their functionality. For example, the BotAction goTo() uses a higher-order function to set the URL for the bot to navigate too, while waitForNavigation does not:

const page = await browser.newPage() // Puppeteer Browser
await goTo('https://example.com')(page) // call higher-order than BotAction
await waitForNavigation(page) // no higher-order, just call BotAction

Higher-order functions are functions that return a function. Botmation uses higher-order functions to customize BotActions during runtime.

Building Bots

Bots are built by assembling BotActions in a line. Let's complete the code above, by assembling it into a bot with a special kind of BotAction, an Assembly Line. The simplest is called chain()():

const bot = chain(
goTo('https://example.com'),
waitForNavigation
)

This bot is assembled by chaining BotActions together.

This is similar to Currying in Functional Programming.

Now let's run the bot:

const bot = chain(
goTo('https://example.com'),
waitForNavigation
)
const page = await browser.newPage() // Puppeteer.Browser
await bot(page) // run with this Puppeteer page

We can run this bot code on multiple Puppeteer pages. We can even run bots concurrently.

An assembled bot is still a BotAction. But, a BotAction is a bot part. Therefore, philosophically, parts and bots are differentiated by observation. BotActions are bot parts, until they are ran as a bot, either individually or in a composition.

Making Simple BotActions

There's three main styles of BotAction code, from simplest to most complex.

The simplest kind have no higher order functions. They are simply async functions. Let's take a look at a familiar example, from Navigation, called waitForNavigation:

const waitForNavigation: BotAction = async(page) => {
await page.waitForNavigation()
}

There are no higher-order functions wrapping this BotAction. It is a single async function that takes a Puppeteer page param to operate on. Simplicity is great.

Making Dynamic BotAction's

BotActions can be customizable by wrapping them in a higher-order function, to provide customizing values for the async functionality. Let's take a closer look at goTo(), from "Navigation" that navigates the page to the URL param:

const goTo = (url: string, goToOptions?: Partial<DirectNavigationOptions>): BotAction =>
async(page) => {
goToOptions = enrichGoToPageOptions(goToOptions)
// same url check
if (page.url() === url) {
return
}
await page.goto(url, goToOptions)
}

goTo() uses a higher-order sync function with two params, url and goToOptions? that customize the returned BotAction function.

The higher order parameters can be whatever you need them to be. They're typed as a spread array of any, so add more if you need more. Also, you are not limited to one higher-order sync function. Stack them up, as high as need be! The possibilities are endless. If you're curious, check out the Loops BotActions for practical BotActions that wrap themselves with two higher order functions.

Composing BotAction's

BotActions with higher-order functions can be used to create static, more readable BotActions:

const goToGoogle = goTo('https://google.com')

However, the best has been saved for last! Assembly Line BotActions can be used to compose complex BotActions that handle broad flows that can be reused across all your bots. For example, how about a single BotAction that logs a bot into a website through a Login form?

const login = (username: string, password: string): BotAction =>
chain(
goTo('https://example.com/login.html'),
click('form input[name="username"]'),
type(username),
click('form input[name="password"]'),
type(password),
click('form button[type="submit"]'),
waitForNavigation,
log('Login Complete')
)

login() is a higher-order sync function that assembles BotActions with the Chain BotAction. The first call of chain() is a regular synchronous function that returns a customized BotAction.

This practical BotAction completes a login flow for a common website form. It starts by going to the login page, entering the username and password into the login page's form's inputs, then submits the form, and finally waits for the Navigation of the page to complete, before logging Login Complete in the NodeJS console.

Edit this page on GitHub
Baby Bot