Botmation Documentation

Pipe

Pipe

These BotAction's manipulate the value of a Pipe object. They should not be used in a Chain.

Map

Map transforms the Pipe value with the provided a pure function.

const map = <R extends PipeValue = PipeValue>(mapFunction: (pipedValue: any) => R): BotAction<R> =>
async (page, ...injects) =>
mapFunction(getInjectsPipeValue(injects))

For an usage example see Instagram's isGuest BotAction.

Pipe Value

This can be used to set the Pipe value for the next BotAction in an Assembly Line.

const pipeValue = <R extends PipeValue = PipeValue>(valueToPipe: R|undefined): BotAction<R|undefined> => async () => valueToPipe

Example:

await pipe()(
pipeValue('Hello World'),
log() // logs 'Pipe: Hello World'
)(page)

Empty Pipe

This BotAction simply returns undefined which empties the Pipe. An empty Pipe is one that value is undefined.

const emptyPipe: BotAction = async () => undefined

Example:

await pipe('Hello World')(
log(), // logs 'Pipe: Hello World'
emptyPipe,
log(), // logs 'Pipe: Empty'
)(page)

Pipe Case

This BotAction is similar to givenThat()() in that it evaluates a condition, or in this case conditions, and if at least one condition passes (is true), then the assembled BotAction's are ran. Instead of providing ConditionalBotAction's, the first call pipeCase() accepts a spread array of PipeValue's, including Functions. The values provided are tested against the pipe object's value. If the value is a function, it's called as a callback with the pipe object's value (as the first param) to test the return value as true.

Pipe Case is similar to an if statement where the conditions of the expression are combined with || or. At least one case must evaluate as true in order for the if statement to be considered true.

const pipeCase =
(...valuesToTest: CaseValue[]) =>
(...actions: BotAction<PipeValue|AbortLineSignal|void>[]): BotAction<PipeValue|AbortLineSignal|CasesSignal> =>
async(page, ...injects) => {
if (injectsHavePipe(injects)) {
const pipeObjectValue = getInjectsPipeValue(injects)
const matches: Dictionary = valuesToTest.reduce((foundMatches, value, index) => {
if (typeof value === 'function') {
if (value(pipeObjectValue)) {
(foundMatches as Dictionary)[index] = value
}
} else {
if (value === pipeObjectValue) {
(foundMatches as Dictionary)[index] = value
}
}
return foundMatches
}, {}) as Dictionary
if (Object.keys(matches).length > 0) {
const returnValue:PipeValue|AbortLineSignal = await pipe()(...actions)(page, ...injects)
if (isAbortLineSignal(returnValue)) {
return processAbortLineSignal(returnValue)
} else {
return createCasesSignal(matches, true, returnValue)
}
} else {
return createCasesSignal(matches, false, pipeObjectValue)
}
}
return createCasesSignal()
}

pipeCase()() similar to pipeCases()() returns an unique JSON object with the CasesSignal type. This standardizes the response to interpret what case(s) matched the pipe object value, whether or not the if statement, as a whole condition, passed. Furthermore, there's an optional pipeValue to carry back from the result of the assembled BotAction's, which are ran in a Pipe.

It takes assembledLines of at least 2 to abort a pipeCase()() from an assembled BotAction. Here's a table that explains it's aborting behavior given the number of assembledLines of an AbortLineSignal returned by an assembled BotAction.

assembledLineseffect
0breaks pipeCase()() line, returns the same AbortLineSignal
1breaks pipeCase()() line, returns a CasesSignal with the AbortLineSignal.pipeValue
2breaks pipeCase()() line, returns AbortLineSignal's pipeValue
3+breaks pipeCase()() line, returns same AbortLineSignal with its assembledLines count reduced by 2

For an example, see LinkedIn's likeUserPostsFrom() BotAction.

Pipe Cases

This BotAction is similar to givenThat()() in that it evaluates a condition, or in this case conditions, and if all conditions pass (are true), then the assembled BotAction's are ran. Instead of providing ConditionalBotAction's, the first call pipeCases() accepts a spread array of PipeValue's, including functions. The values provided are tested against the pipe object's value. If the value is a function, it's called as a callback with the pipe object's value (as the first param) to test the return value as true.

Pipe Cases is similar to an if statement where the conditions of the expression are combined with && and. All of the cases must evaluate to true, in order for the if statement to be considered true.

const pipeCases =
(...valuesToTest: CaseValue[]) =>
(...actions: BotAction<PipeValue|AbortLineSignal|void>[]): BotAction<PipeValue|AbortLineSignal|CasesSignal> =>
async(page, ...injects) => {
const matches: Dictionary = {}
if (injectsHavePipe(injects)) {
const pipeObjectValue = getInjectsPipeValue(injects)
for (const [i, value] of valuesToTest.entries()) {
if (typeof value === 'function') {
if (value(pipeObjectValue)) {
matches[i] = value
} else {
break
}
} else {
if (value === pipeObjectValue) {
matches[i] = value
} else {
break
}
}
}
if (Object.keys(matches).length === valuesToTest.length) {
const returnValue:PipeValue|AbortLineSignal = await pipe()(...actions)(page, ...injects)
if (isAbortLineSignal(returnValue)) {
return processAbortLineSignal(returnValue)
} else {
return createCasesSignal(matches, true, returnValue)
}
} else {
return createCasesSignal(matches, false, pipeObjectValue)
}
}
return createCasesSignal(matches)
}

pipeCases()() similar to pipeCase()() returns an unique JSON object with the CasesSignal type. This standardizes the response to interpret what case(s) matched the pipe object value, whether or not the if statement, as a whole condition, passed. Furthermore, there's an optional pipeValue to carry back from the result of the assembled BotAction's, which are ran in a Pipe.

It takes assembledLines of at least 2 to abort a pipeCase()() from an assembled BotAction. Here's a table that explains it's aborting behavior given the number of assembledLines of an AbortLineSignal returned by an assembled BotAction.

assembledLineseffect
0breaks pipeCase()() line, returns the same AbortLineSignal
1breaks pipeCase()() line, returns a CasesSignal with the AbortLineSignal.pipeValue
2breaks pipeCase()() line, returns AbortLineSignal's pipeValue
3+breaks pipeCase()() line, returns same AbortLineSignal with its assembledLines count reduced by 2

For an example, see LinkedIn's likeUserPostsFrom() BotAction.

Helpers

These Helpers are for all BotAction's, including the ones focused just on Chain. They are functions designed to be Assembly Line safe, for creating BotAction's that can safely use a Pipe, but safely run in Chain with safe fallbacks.

unpipeInjects()

This function allows you to unwrap the Pipe value from the Pipe object and specify the number of injects that you are expecting. Therefore, if the BotAction runs with less injects, it can fill them with undefined or what you specify.

const unpipeInjects = <P extends PipeValue = PipeValue>(injectsMaybePiped: any[], minimumInjectsCount = 0, minimumInjectsFill = undefined): [P, ...any[]] => {
if (minimumInjectsCount > 0 && injectsMaybePiped.length < minimumInjectsCount) {
const minimumInjects = []
for(let i = 0; i < minimumInjectsCount; i++) {
// check for inject AND that the inject isn't a Pipe
if (injectsMaybePiped[i] && !isPipe(injectsMaybePiped[i])) {
minimumInjects.push(injectsMaybePiped[i])
} else {
minimumInjects.push(minimumInjectsFill)
}
}
return [getInjectsPipeValue(injectsMaybePiped), ...minimumInjects]
}
return [getInjectsPipeValue(injectsMaybePiped), ...removePipe(injectsMaybePiped)]
}

This way you can safely use a Pipe with injects when you may not get the injects you want or even a Pipe object.

removePipe()

This function removes the Pipe object from injects if injects have it.

const removePipe = (injects: any[]): any[] => {
if (injectsHavePipe(injects)) {
return injects.slice(0, injects.length - 1)
}
return injects
}

It is helpful in making BotAction's that are Chain focused.

getInjectsPipeOrEmptyPipe()

This function gets the Pipe object from injects. If injects is missing the Pipe object, it returns an empty Pipe with the value undefined.

const getInjectsPipeOrEmptyPipe = <P = any>(injects: any[]): Pipe<P> =>
injects.length > 0 && isPipe(injects[injects.length - 1]) ? injects[injects.length - 1] : createEmptyPipe()

This is helpful for creating BotAction's where you want to use the Pipe object safely, even if there is none ie when assembled in a Chain.

createEmptyPipe()

This function creates an empty Pipe object.

const createEmptyPipe = (): EmptyPipe => wrapValueInPipe()

The default Pipe object in a Pipe is this. When a BotAction, in a Pipe, doesn't return a value, the next BotAction is injected an empty Pipe object.

wrapValueInPipe()

This function creates a Pipe object with the value provided.

const wrapValueInPipe = <P = any>(value?: P): Pipe<P> => ({
brand: 'Pipe',
value
})

This function is helpful for writing Assembly Line BotAction's that deal with piping.

injectsHavePipe()

This function returns a boolean for whether or not the injects have a Pipe object.

const injectsHavePipe = (injects: any[]): boolean => {
if (injects.length === 0) {
return false
}
return isPipe(injects[injects.length - 1])
}

This is a way to determine if the BotAction is assembled in a Pipe or a Chain. If the injects has a Pipe object, then it's assembled in a Chain.

getInjectsPipeValue()

This function returns the Pipe object's value from injects. If the injects don't have a Pipe object, then the value undefined is returned.

const getInjectsPipeValue = (injects: any[]): any => {
if (injectsHavePipe(injects)) {
return injects[injects.length - 1].value
}
return undefined
}

This function is helpful for writing BotAction's that want to use the Pipe value safely, even in a Chain, when you're not interested in the injects.

pipeInjects()

This function adds an empty Pipe object to injects if the injects don't have a Pipe object.

const pipeInjects = (injects: any[]): any[] => {
if (injectsHavePipe(injects)) {
return injects
}
return [...injects, createEmptyPipe()]
}

This can be used to simulate a BotAction being assembled in a Pipe.

createCasesSignal()

This function is used by pipeCase()() and pipeCases()() to create a CasesSignal object to return. If no params are provided, the safe default is a CasesSignal that has no matches, the overall condition did not pass, and the pipe value is undefined.

This function is found in a separate helpers/cases.ts file

const createCasesSignal = <V = any>(matches: Dictionary<V> = {}, conditionPass: boolean = false, pipeValue?: PipeValue): CasesSignal<V> => ({
brand: 'Cases_Signal',
conditionPass,
matches,
pipeValue
})

casesSignalToPipeValue()

This function is helpful with the map() BotAction. It can be provided directly as a means to map the CasesSignal object to its pipe value. Therefore, when combined after a pipeCase()() call, it will change the pipe object value to the CasesSignal.pipeValue.

This function is found in a separate helpers/cases.ts file

const casesSignalToPipeValue = (casesSignal: CasesSignal|any): PipeValue =>
isCasesSignal(casesSignal) ? casesSignal.pipeValue : undefined

Example:

await pipe(5)(
pipeCase(5)(
// some actions that will run
// last BotAction returns a value
),
// CasesSignal has pipeValue with last returned value
map(casesSignalToPipeValue),
log('Cases Signal pipe value')
)(page)

isCasesSignal()

This function is a type guard for the CasesSignal type. It's found in the types/cases.ts file.

const isCasesSignal = <V = any>(value: any): value is CasesSignal<V> =>
typeof value === 'object' &&
value !== null &&
value.brand === 'Cases_Signal' &&
typeof value.conditionPass === 'boolean' &&
isDictionary<V>(value.matches)
Edit this page on GitHub
Baby Bot